일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 매터
- RS-485
- 힐스테이트 광교산
- 오블완
- 티스토리챌린지
- 공모주
- 배당
- 월패드
- 국내주식
- 미국주식
- 나스닥
- Espressif
- Bestin
- raspberry pi
- MQTT
- ConnectedHomeIP
- matter
- Home Assistant
- Python
- 애플
- 현대통신
- esp32
- homebridge
- SK텔레콤
- 코스피
- Apple
- 홈네트워크
- 파이썬
- cluster
- 해외주식
- Today
- Total
YOGYUI
광교아이파크::엘리베이터 Apple 홈킷 연동 (3) 본문
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 버튼을 클릭하면 소형 월패드의 '하향 호출 버튼'이 빨간색으로 점등되며 엘리베이터가 운행중 - 도착 으로 상태가 바뀜을 알 수 있다
이제 Homekit과 연동할 모든 준비가 완료되었다!
[시리즈 링크]
'홈네트워크(IoT) > 광교아이파크' 카테고리의 다른 글
광교아이파크::거실 조명 Apple 홈킷 연동 (1) (4) | 2021.02.15 |
---|---|
광교아이파크::엘리베이터 Apple 홈킷 연동 (4) - Final (6) | 2021.01.24 |
광교아이파크::엘리베이터 Apple 홈킷 연동 (2) (0) | 2021.01.23 |
광교아이파크::엘리베이터 Apple 홈킷 연동 (1) (0) | 2021.01.20 |
광교아이파크::가스 Apple 홈킷 연동 (3) - Final (0) | 2021.01.06 |