YOGYUI

광교아이파크::엘리베이터 Apple 홈킷 연동 (3) 본문

홈네트워크(IoT)/광교아이파크

광교아이파크::엘리베이터 Apple 홈킷 연동 (3)

요겨 2021. 1. 24. 03:11
반응형

3. Implementation

우선, 256개 Timestamp에 대한 하행 호출 패킷을 모으기 위해 다음과 같이 코드를 짜봤다

(패킷을 리스트에 담는데, 동일한 timestamp 값을 모은적이 있다면 패스, 리스트 자체를 직렬화해서 로컬에 저장)

import os
from SerialComm import SerialComm
from SmartParser import SmartParser

if __name__ == '__main__':
    import time
    import pickle

    picklepath = './smart_elevator_down_packets.pkl'
    
    os.system('clear')
    ser1 = SerialComm()
    ser2 = SerialComm()
    par = SmartParser(ser1, ser2)
    par.enable_console_log = False

    down_packet_list = []
    if os.path.isfile(picklepath):
        with open(picklepath, 'rb') as fp:
            down_packet_list = pickle.load(fp)

    print('--- Found Count = {} ---'.format(len(down_packet_list)))
    exist = [x[4] for x in down_packet_list]
    remain = []
    for i in range(256):
        if i not in exist:
            remain.append(i)
    print('Remain: ' + str(remain))

    def onParse1(chunk):
        try:
            timestamp = chunk[4]
            print('%03d' % timestamp, end='\r')
        except Exception as e:
            print(e)

    def onParse2(chunk):
        msg = ' '.join(['%02X' % x for x in chunk])
        if chunk[3] == 0x91 and chunk[5] == 0x10:
            find = list(filter(lambda x: x[4] == chunk[4], down_packet_list))
            if len(find) == 0:
                print(msg + ' ({})'.format(len(down_packet_list)))
                down_packet_list.append(chunk)
                with open(picklepath, 'wb') as fp:
                    pickle.dump(down_packet_list, fp)

    par.sig_parse1.connect(onParse1)
    par.sig_parse2.connect(onParse2)

    ser1.connect('/dev/ttyUSB2', 9600)
    ser2.connect('/dev/ttyUSB3', 9600)

    while True:
        if len(down_packet_list) >= 256:
            break
    
    down_packet_list.sort(key=lambda x: x[4])
    print('--- Found All ---')
    for e in down_packet_list:
        msg = ' '.join(['%02X' % x for x in e])
        print(msg)
    
    ser1.release()
    ser2.release()

엄청난 노가다 끝에 256개 엘리베이터 하행 호출 응답 패킷 모으는데 성공 (거의 30분쯤 걸린듯.. 원래는 상행 호출도 같이 구현하려고 했는데, 귀찮아서 포기)

라즈베리파이 로컬에 직렬화해두긴 했는데, 혹시 모를 사태를 대비해 블로그에도 텍스트형식으로 남겨놓자

더보기

02 C1 0C 91 00 10 01 00 02 01 02 4E
02 C1 0C 91 01 10 01 00 02 01 02 51
02 C1 0C 91 02 10 01 00 02 01 02 50
02 C1 0C 91 03 10 01 00 02 01 02 53
02 C1 0C 91 04 10 01 00 02 01 02 52
02 C1 0C 91 05 10 01 00 02 01 02 55
02 C1 0C 91 06 10 01 00 02 01 02 74
02 C1 0C 91 07 10 01 00 02 01 02 57
02 C1 0C 91 08 10 01 00 02 01 02 46
02 C1 0C 91 09 10 01 00 02 01 02 49
02 C1 0C 91 0A 10 01 00 02 01 02 48
02 C1 0C 91 0B 10 01 00 02 01 02 4B
02 C1 0C 91 0C 10 01 00 02 01 02 4A
02 C1 0C 91 0D 10 01 00 02 01 02 4D
02 C1 0C 91 0E 10 01 00 02 01 02 4C
02 C1 0C 91 0F 10 01 00 02 01 02 4F
02 C1 0C 91 10 10 01 00 02 01 02 5E
02 C1 0C 91 11 10 01 00 02 01 02 61
02 C1 0C 91 12 10 01 00 02 01 02 60
02 C1 0C 91 13 10 01 00 02 01 02 63
02 C1 0C 91 14 10 01 00 02 01 02 62
02 C1 0C 91 15 10 01 00 02 01 02 65
02 C1 0C 91 16 10 01 00 02 01 02 44
02 C1 0C 91 17 10 01 00 02 01 02 67
02 C1 0C 91 18 10 01 00 02 01 02 56
02 C1 0C 91 19 10 01 00 02 01 02 59
02 C1 0C 91 1A 10 01 00 02 01 02 58
02 C1 0C 91 1B 10 01 00 02 01 02 5B
02 C1 0C 91 1C 10 03 00 02 01 02 58
02 C1 0C 91 1D 10 01 00 02 01 02 5D
02 C1 0C 91 1E 10 01 00 02 01 02 5C
02 C1 0C 91 1F 10 01 00 02 01 02 5F
02 C1 0C 91 20 10 01 00 02 01 02 6E
02 C1 0C 91 21 10 01 00 02 01 02 71
02 C1 0C 91 22 10 01 00 02 01 02 70
02 C1 0C 91 23 10 01 00 02 01 02 73
02 C1 0C 91 24 10 01 00 02 01 02 72
02 C1 0C 91 25 10 01 00 02 01 02 75
02 C1 0C 91 26 10 01 00 02 01 02 94
02 C1 0C 91 27 10 01 00 02 01 02 77
02 C1 0C 91 28 10 01 00 02 01 02 66
02 C1 0C 91 29 10 01 00 02 01 02 69
02 C1 0C 91 2A 10 01 00 02 01 02 68
02 C1 0C 91 2B 10 01 00 02 01 02 6B
02 C1 0C 91 2C 10 01 00 02 01 02 6A
02 C1 0C 91 2D 10 01 00 02 01 02 6D
02 C1 0C 91 2E 10 01 00 02 01 02 6C
02 C1 0C 91 2F 10 01 00 02 01 02 6F
02 C1 0C 91 30 10 01 00 02 01 02 7E
02 C1 0C 91 31 10 01 00 02 01 02 81
02 C1 0C 91 32 10 01 00 02 01 02 80
02 C1 0C 91 33 10 01 00 02 01 02 83
02 C1 0C 91 34 10 01 00 02 01 02 82
02 C1 0C 91 35 10 01 00 02 01 02 85
02 C1 0C 91 36 10 01 00 02 01 02 64
02 C1 0C 91 37 10 01 00 02 01 02 87
02 C1 0C 91 38 10 01 00 02 01 02 76
02 C1 0C 91 39 10 01 00 02 01 02 79
02 C1 0C 91 3A 10 01 00 02 01 02 78
02 C1 0C 91 3B 10 01 00 02 01 02 7B
02 C1 0C 91 3C 10 01 00 02 01 02 7A
02 C1 0C 91 3D 10 01 00 02 01 02 7D
02 C1 0C 91 3E 10 01 00 02 01 02 7C
02 C1 0C 91 3F 10 01 00 02 01 02 7F
02 C1 0C 91 40 10 01 00 02 01 02 0E
02 C1 0C 91 41 10 01 00 02 01 02 11
02 C1 0C 91 42 10 01 00 02 01 02 10
02 C1 0C 91 43 10 01 00 02 01 02 13
02 C1 0C 91 44 10 01 00 02 01 02 12
02 C1 0C 91 45 10 01 00 02 01 02 15
02 C1 0C 91 46 10 01 00 02 01 02 34
02 C1 0C 91 47 10 01 00 02 01 02 17
02 C1 0C 91 48 10 01 00 02 01 02 06
02 C1 0C 91 49 10 01 00 02 01 02 09
02 C1 0C 91 4A 10 01 00 02 01 02 08
02 C1 0C 91 4B 10 01 00 02 01 02 0B
02 C1 0C 91 4C 10 01 00 02 01 02 0A
02 C1 0C 91 4D 10 01 00 02 01 02 0D
02 C1 0C 91 4E 10 01 00 02 01 02 0C
02 C1 0C 91 4F 10 01 00 02 01 02 0F
02 C1 0C 91 50 10 01 00 02 01 02 1E
02 C1 0C 91 51 10 01 00 02 01 02 21
02 C1 0C 91 52 10 01 00 02 01 02 20
02 C1 0C 91 53 10 01 00 02 01 02 23
02 C1 0C 91 54 10 01 00 02 01 02 22
02 C1 0C 91 55 10 01 00 02 01 02 25
02 C1 0C 91 56 10 01 00 02 01 02 04
02 C1 0C 91 57 10 01 00 02 01 02 27
02 C1 0C 91 58 10 01 00 02 01 02 16
02 C1 0C 91 59 10 01 00 02 01 02 19
02 C1 0C 91 5A 10 01 00 02 01 02 18
02 C1 0C 91 5B 10 01 00 02 01 02 1B
02 C1 0C 91 5C 10 01 00 02 01 02 1A
02 C1 0C 91 5D 10 01 00 02 01 02 1D
02 C1 0C 91 5E 10 01 00 02 01 02 1C
02 C1 0C 91 5F 10 01 00 02 01 02 1F
02 C1 0C 91 60 10 01 00 02 01 02 2E
02 C1 0C 91 61 10 01 00 02 01 02 31
02 C1 0C 91 62 10 01 00 02 01 02 30
02 C1 0C 91 63 10 01 00 02 01 02 33
02 C1 0C 91 64 10 01 00 02 01 02 32
02 C1 0C 91 65 10 01 00 02 01 02 35
02 C1 0C 91 66 10 01 00 02 01 02 54
02 C1 0C 91 67 10 01 00 02 01 02 37
02 C1 0C 91 68 10 02 00 02 01 02 27
02 C1 0C 91 69 10 01 00 02 01 02 29
02 C1 0C 91 6A 10 01 00 02 01 02 28
02 C1 0C 91 6B 10 01 00 02 01 02 2B
02 C1 0C 91 6C 10 01 00 02 01 02 2A
02 C1 0C 91 6D 10 01 00 02 01 02 2D
02 C1 0C 91 6E 10 01 00 02 01 02 2C
02 C1 0C 91 6F 10 01 00 02 01 02 2F
02 C1 0C 91 70 10 01 00 02 01 02 3E
02 C1 0C 91 71 10 01 00 02 01 02 41
02 C1 0C 91 72 10 01 00 02 01 02 40
02 C1 0C 91 73 10 01 00 02 01 02 43
02 C1 0C 91 74 10 01 00 02 01 02 42
02 C1 0C 91 75 10 01 00 02 01 02 45
02 C1 0C 91 76 10 01 00 02 01 02 24
02 C1 0C 91 77 10 01 00 02 01 02 47
02 C1 0C 91 78 10 01 00 02 01 02 36
02 C1 0C 91 79 10 01 00 02 01 02 39
02 C1 0C 91 7A 10 01 00 02 01 02 38
02 C1 0C 91 7B 10 01 00 02 01 02 3B
02 C1 0C 91 7C 10 01 00 02 01 02 3A
02 C1 0C 91 7D 10 01 00 02 01 02 3D
02 C1 0C 91 7E 10 01 00 02 01 02 3C
02 C1 0C 91 7F 10 01 00 02 01 02 3F
02 C1 0C 91 80 10 01 00 02 01 02 CE
02 C1 0C 91 81 10 01 00 02 01 02 D1
02 C1 0C 91 82 10 01 00 02 01 02 D0
02 C1 0C 91 83 10 01 00 02 01 02 D3
02 C1 0C 91 84 10 01 00 02 01 02 D2
02 C1 0C 91 85 10 01 00 02 01 02 D5
02 C1 0C 91 86 10 01 00 02 01 02 F4
02 C1 0C 91 87 10 01 00 02 01 02 D7
02 C1 0C 91 88 10 01 00 02 01 02 C6
02 C1 0C 91 89 10 01 00 02 01 02 C9
02 C1 0C 91 8A 10 01 00 02 01 02 C8
02 C1 0C 91 8B 10 01 00 02 01 02 CB
02 C1 0C 91 8C 10 01 00 02 01 02 CA
02 C1 0C 91 8D 10 01 00 02 01 02 CD
02 C1 0C 91 8E 10 01 00 02 01 02 CC
02 C1 0C 91 8F 10 01 00 02 01 02 CF
02 C1 0C 91 90 10 01 00 02 01 02 DE
02 C1 0C 91 91 10 01 00 02 01 02 E1
02 C1 0C 91 92 10 01 00 02 01 02 E0
02 C1 0C 91 93 10 01 00 02 01 02 E3
02 C1 0C 91 94 10 01 00 02 01 02 E2
02 C1 0C 91 95 10 01 00 02 01 02 E5
02 C1 0C 91 96 10 01 00 02 01 02 C4
02 C1 0C 91 97 10 01 00 02 01 02 E7
02 C1 0C 91 98 10 01 00 02 01 02 D6
02 C1 0C 91 99 10 01 00 02 01 02 D9
02 C1 0C 91 9A 10 01 00 02 01 02 D8
02 C1 0C 91 9B 10 01 00 02 01 02 DB
02 C1 0C 91 9C 10 01 00 02 01 02 DA
02 C1 0C 91 9D 10 01 00 02 01 02 DD
02 C1 0C 91 9E 10 01 00 02 01 02 DC
02 C1 0C 91 9F 10 01 00 02 01 02 DF
02 C1 0C 91 A0 10 01 00 02 01 02 EE
02 C1 0C 91 A1 10 01 00 02 01 02 F1
02 C1 0C 91 A2 10 01 00 02 01 02 F0
02 C1 0C 91 A3 10 01 00 02 01 02 F3
02 C1 0C 91 A4 10 01 00 02 01 02 F2
02 C1 0C 91 A5 10 01 00 02 01 02 F5
02 C1 0C 91 A6 10 01 00 02 01 02 14
02 C1 0C 91 A7 10 01 00 02 01 02 F7
02 C1 0C 91 A8 10 01 00 02 01 02 E6
02 C1 0C 91 A9 10 01 00 02 01 02 E9
02 C1 0C 91 AA 10 01 00 02 01 02 E8
02 C1 0C 91 AB 10 01 00 02 01 02 EB
02 C1 0C 91 AC 10 01 00 02 01 02 EA
02 C1 0C 91 AD 10 01 00 02 01 02 ED
02 C1 0C 91 AE 10 01 00 02 01 02 EC
02 C1 0C 91 AF 10 01 00 02 01 02 EF
02 C1 0C 91 B0 10 01 00 02 01 02 FE
02 C1 0C 91 B1 10 01 00 02 01 02 01
02 C1 0C 91 B2 10 01 00 02 01 02 00
02 C1 0C 91 B3 10 01 00 02 01 02 03
02 C1 0C 91 B4 10 01 00 02 01 02 02
02 C1 0C 91 B5 10 01 00 02 01 02 05
02 C1 0C 91 B6 10 01 00 02 01 02 E4
02 C1 0C 91 B7 10 01 00 02 01 02 07
02 C1 0C 91 B8 10 01 00 02 01 02 F6
02 C1 0C 91 B9 10 01 00 02 01 02 F9
02 C1 0C 91 BA 10 01 00 02 01 02 F8
02 C1 0C 91 BB 10 01 00 02 01 02 FB
02 C1 0C 91 BC 10 01 00 02 01 02 FA
02 C1 0C 91 BD 10 01 00 02 01 02 FD
02 C1 0C 91 BE 10 01 00 02 01 02 FC
02 C1 0C 91 BF 10 01 00 02 01 02 FF
02 C1 0C 91 C0 10 01 00 02 01 02 8E
02 C1 0C 91 C1 10 01 00 02 01 02 91
02 C1 0C 91 C2 10 01 00 02 01 02 90
02 C1 0C 91 C3 10 01 00 02 01 02 93
02 C1 0C 91 C4 10 01 00 02 01 02 92
02 C1 0C 91 C5 10 01 00 02 01 02 95
02 C1 0C 91 C6 10 01 00 02 01 02 B4
02 C1 0C 91 C7 10 01 00 02 01 02 97
02 C1 0C 91 C8 10 01 00 02 01 02 86
02 C1 0C 91 C9 10 01 00 02 01 02 89
02 C1 0C 91 CA 10 01 00 02 01 02 88
02 C1 0C 91 CB 10 01 00 02 01 02 8B
02 C1 0C 91 CC 10 01 00 02 01 02 8A
02 C1 0C 91 CD 10 01 00 02 01 02 8D
02 C1 0C 91 CE 10 01 00 02 01 02 8C
02 C1 0C 91 CF 10 01 00 02 01 02 8F
02 C1 0C 91 D0 10 01 00 02 01 02 9E
02 C1 0C 91 D1 10 01 00 02 01 02 A1
02 C1 0C 91 D2 10 01 00 02 01 02 A0
02 C1 0C 91 D3 10 01 00 02 01 02 A3
02 C1 0C 91 D4 10 01 00 02 01 02 A2
02 C1 0C 91 D5 10 01 00 02 01 02 A5
02 C1 0C 91 D6 10 01 00 02 01 02 84
02 C1 0C 91 D7 10 01 00 02 01 02 A7
02 C1 0C 91 D8 10 01 00 02 01 02 96
02 C1 0C 91 D9 10 01 00 02 01 02 99
02 C1 0C 91 DA 10 01 00 02 01 02 98
02 C1 0C 91 DB 10 01 00 02 01 02 9B
02 C1 0C 91 DC 10 01 00 02 01 02 9A
02 C1 0C 91 DD 10 01 00 02 01 02 9D
02 C1 0C 91 DE 10 01 00 02 01 02 9C
02 C1 0C 91 DF 10 01 00 02 01 02 9F
02 C1 0C 91 E0 10 01 00 02 01 02 AE
02 C1 0C 91 E1 10 01 00 02 01 02 B1
02 C1 0C 91 E2 10 01 00 02 01 02 B0
02 C1 0C 91 E3 10 01 00 02 01 02 B3
02 C1 0C 91 E4 10 01 00 02 01 02 B2
02 C1 0C 91 E5 10 01 00 02 01 02 B5
02 C1 0C 91 E6 10 01 00 02 01 02 D4
02 C1 0C 91 E7 10 01 00 02 01 02 B7
02 C1 0C 91 E8 10 01 00 02 01 02 A6
02 C1 0C 91 E9 10 01 00 02 01 02 A9
02 C1 0C 91 EA 10 01 00 02 01 02 A8
02 C1 0C 91 EB 10 01 00 02 01 02 AB
02 C1 0C 91 EC 10 01 00 02 01 02 AA
02 C1 0C 91 ED 10 01 00 02 01 02 AD
02 C1 0C 91 EE 10 01 00 02 01 02 AC
02 C1 0C 91 EF 10 01 00 02 01 02 AF
02 C1 0C 91 F0 10 01 00 02 01 02 BE
02 C1 0C 91 F1 10 01 00 02 01 02 C1
02 C1 0C 91 F2 10 01 00 02 01 02 C0
02 C1 0C 91 F3 10 01 00 02 01 02 C3
02 C1 0C 91 F4 10 01 00 02 01 02 C2
02 C1 0C 91 F5 10 01 00 02 01 02 C5
02 C1 0C 91 F6 10 01 00 02 01 02 A4
02 C1 0C 91 F7 10 01 00 02 01 02 C7
02 C1 0C 91 F8 10 01 00 02 01 02 B6
02 C1 0C 91 F9 10 01 00 02 01 02 B9
02 C1 0C 91 FA 10 01 00 02 01 02 B8
02 C1 0C 91 FB 10 01 00 02 01 02 BB
02 C1 0C 91 FC 10 01 00 02 01 02 BA
02 C1 0C 91 FD 10 01 00 02 01 02 BD
02 C1 0C 91 FE 10 01 00 02 01 02 BC
02 C1 0C 91 FF 10 01 00 02 01 02 BF

이제 호출 동작이 제대로 작동하는지 확인하기 위해 간단하게 HTTP 서버를 만들어보자

# app.py
import ctypes
from typing import Union
from flask import Flask, request
from SerialComm import SerialComm

class Elevator:
    buffer1: bytearray = bytearray()
    buffer2: bytearray = bytearray()
    timestamp: int = -1
    state: int = -1
    current_floor: int = -1
    flag_moving: int = False
    flag_send_down_packet: bool = False
    elevator_down_packets: List[str]
    
    def __init__(self, ser1, ser2):
        self.ser1 = ser1
        self.ser2 = ser2
        ser1.sig_recv_data.connect(self.onRecvData1)
        # ser2.sig_recv_data.connect(self.onRecvData2)

    def onRecvData1(self, data: bytes):
        self.buffer1.extend(data)
        idx = self.buffer1.find(0x2)
        if idx > 0:
            self.buffer1 = self.buffer1[idx:]
        packlen = self.buffer1[2]
        self.timestamp1 = self.buffer1[4]
        self.flag_moving = self.buffer1[11]
        if self.flag_moving:  # 0=stopped, 1=moving, 4:arrived
            self.flag_send_down_packet = False
        chunk = self.buffer[:packlen]
        if chunk[3] == 0x11 and self.flag_send_down_packet:
            packet = self.elevator_down_packets[self.timestamp1]
            self.ser2.sendData(bytearray([int(x, 16) for x in packet.split(' ')]))
        elif chunk[3] == 0x13:
            self.state = chunk[11]
            self.current_floor = ctypes.c_int8(chunk[12]).value
        self.buffer1 = self.buffer1[packlen:]
        
    def call_down(self):
        self.flag_send_down_packet = True

app = Flask(__name__)
ser1 = SerialComm()
ser2 = SerialComm()
elevator = Elevator(ser1, ser2)

@app.route('/elevator', methods=['GET', 'POST'])
def elevator_process():
    req = request.get_data().decode(encoding='utf-8')
    if 'command' in req:
        elevator.call_down()
    from datetime import datetime
    return render_template(
        "elevator.html",
        time=datetime.now(),
        state=elevator.state, 
        current_floor=elevator.current_floor
        )

@app.route('/elevator/update', methods=['GET', 'POST'])
def elevator_update():
    from datetime import datetime
    now = datetime.now()

    return jsonify({
        'time': now.strftime("%Y-%m-%d %H:%M:%S"),
        'state': elevator.state,
        'current_floor': elevator.current_floor
    })

if __name__ == '__main__':
    ser1.connect('/dev/ttyUSB2', 9600)
    ser2.connect('/dev/ttyUSB3', 9600)
    app.run(host='0.0.0.0', port=9999)
<!-- templates/elevator.html -->
<!DOCTYPE html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
    <h1>[ Current Time] </h1>
    <h1 id="time">
        {% if time %}
            {{ time }}
        {% endif %}
    </h1>
    <h1>[ State ]</h1>
    <h1 id="state">
        {% if state == 1 %}
            MOVING
        {% elif state == 4 %}
            ARRIVED
        {% else %}
            STOPPED
        {% endif %}
    </h1>
    <!-- 개인정보 보호를 위해 현재 층은 표시하지 않는다
    <h1>[ Current Floor ]</h1>
    <h1 id="current_floor">
        {% if current_floor %}
            {{ current_floor }}
        {% endif %}
    </h1>
    -->

    <form method="post" action="/elevator", enctype="text/plain">
        <input type="submit" value="CALL" name="command" style="font-size: 8em;">
    </form>

    <script>
        setInterval(function() { $.ajax({
            url: '/elevator/update',
            type: 'POST',
            success: function(response) {
                $("#time").html(response["time"]);
                $("#current_floor").html(response["current_floor"]);
                if (response["state"] == 1) {
                    $("#state").html("MOVING");
                } else if (response["state"] == 4) {
                    $("#state").html("ARRIVED");
                } else {
                    $("#state").html("STOPPED");
                }
            },
            error: function(error) {
                console.log(error);
            }
        })}, 250);
    </script>
</body>

Flask 앱 실행 후 웹브라우저로 접속(localhost:9999/elevator)하면 실시간으로 현재 시간이 업데이트되는 페이지가 열린다

CALL 버튼을 클릭하면 소형 월패드의 '하향 호출 버튼'이 빨간색으로 점등되며 엘리베이터가 운행중 - 도착 으로 상태가 바뀜을 알 수 있다 

HTTP를 통한 Bentin 엘리베이터 호출

이제 Homekit과 연동할 모든 준비가 완료되었다!

 

[시리즈 링크]

광교아이파크::엘리베이터 Apple 홈킷 연동 (1)

광교아이파크::엘리베이터 Apple 홈킷 연동 (2)

광교아이파크::엘리베이터 Apple 홈킷 연동 (3)

광교아이파크::엘리베이터 Apple 홈킷 연동 (4)

반응형