diff --git a/rpiKivyDash/cmders.json b/rpiKivyDash/cmders.json new file mode 100644 index 0000000..37ac0d4 --- /dev/null +++ b/rpiKivyDash/cmders.json @@ -0,0 +1,10 @@ +[ + {"id": 1, "icons_path": "icons/calc.png", "cmd": "calc"}, + {"id": 2, "icons_path": "icons/notepad.png", "cmd": "notepad"}, + {"id": 3, "icons_path": "icons/gemini.png", "cmd": "gemini"}, + {"id": 4, "icons_path": "", "cmd": "copilot"}, + {"id": 5, "icons_path": "", "cmd": "calc"}, + {"id": 6, "icons_path": "", "cmd": "calc"}, + {"id": 7, "icons_path": "", "cmd": "calc"}, + {"id": 8, "icons_path": "", "cmd": "calc"} +] diff --git a/rpiKivyDash/main.py b/rpiKivyDash/main.py new file mode 100644 index 0000000..9f95f3a --- /dev/null +++ b/rpiKivyDash/main.py @@ -0,0 +1,185 @@ +import json +import bluetooth +import threading +from kivy.config import Config +import time +import sys + +# Kivy 앱의 크기와 설정을 고정합니다. +# 풀스크린과 테두리 없음을 설정하여 라즈베리파이 LCD에 맞춰 실행되도록 합니다. +Config.set('graphics', 'fullscreen', '1') +Config.set('graphics', 'borderless', '1') +Config.set('graphics', 'resizable', '0') +Config.set('graphics', 'width', '480') +Config.set('graphics', 'height', '320') +Config.set('graphics', 'show_cursor', '0') + +# 서버의 블루투스 MAC 주소와 UUID를 설정합니다. +# 이 값은 갤럭시북의 정보와 일치해야 합니다. +SERVER_MAC = '8C:E9:EE:C9:33:4D' +PORT = 5 + + +from kivy.app import App +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.gridlayout import GridLayout +from kivy.uix.label import Label +from kivy.uix.image import Image +from kivy.uix.behaviors import ButtonBehavior +from kivy.core.window import Window +from kivy.graphics import Color, RoundedRectangle +from kivy.clock import Clock + + +# Kivy 애플리케이션 클래스 +class DashboardApp(App): + def build(self): + self.client_sock = None + self.is_connected = False + self.status_label = Label(text="Connecting...", font_size=20, color=(1, 1, 0, 1)) + + # Kivy UI 구성 + root = BoxLayout(orientation='vertical', padding=10) + + title = Label(text="MK's GalaxyBook5 Dashboard", font_size=25, size_hint_y=None, height=40) + root.add_widget(title) + + grid = GridLayout(cols=4, rows=2, spacing=10, padding=1) + + # cmder.json 파일에서 버튼 데이터 로드 + try: + with open("cmders.json", "r", encoding="utf-8") as f: + self.cmders = json.load(f) + except FileNotFoundError: + self.cmders = [] + print("cmders.json 파일을 찾을 수 없습니다. 기본값으로 실행합니다.") + + for item in self.cmders: + icon_path = item["icons_path"] if item["icons_path"] else "icons/default.png" + cmd = item["cmd"] + btn = IconButton(icon_path=icon_path, cmd=cmd, app_instance=self) + grid.add_widget(btn) + + root.add_widget(grid) + + self.status_label.text = "Attempting to connect..." + self.status_label.size_hint_y = None + self.status_label.height = 30 + root.add_widget(self.status_label) + + # 연결 시도 시작 + print("[DEBUG] 초기 연결 시도 시작...") + self.connect_scheduled_event = Clock.schedule_once(self.connect_to_server, 1) + + return root + + def connect_to_server(self, dt=0): + """서버에 연결을 시도하는 함수입니다.""" + if self.client_sock: + self.client_sock.close() + self.client_sock = None + + self.is_connected = False + self.update_status_label("Connecting...") + print(f"[DEBUG] 연결 시도: MAC={SERVER_MAC}, PORT={PORT}") + + try: + self.client_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) + # self.client_sock.settimeout(5.0) + self.client_sock.connect((SERVER_MAC, PORT)) + self.is_connected = True + self.update_status_label("Connected!") + print("[DEBUG] 연결 성공!") + + # 연결 성공 시 5초마다 상태 업데이트 스케줄링 + Clock.schedule_interval(self.get_system_status, 5) + + except bluetooth.btcommon.BluetoothError as e: + self.update_status_label(f"Connection Failed: {e}") + print(f"[DEBUG] 연결 실패: {e}. 5초 후 재시도...") + if self.client_sock: + self.client_sock.close() + self.client_sock = None + # 연결 실패 시 5초 후 재시도 + Clock.schedule_once(self.connect_to_server, 5) + + + def get_system_status(self, dt): + """서버에 시스템 상태를 요청하고 응답을 받습니다.""" + if not self.is_connected or not self.client_sock: + print("[DEBUG] 연결이 끊어져 상태 업데이트 중단.") + Clock.unschedule(self.get_system_status) + self.connect_to_server() + return + + try: + self.client_sock.send("get_status".encode('utf-8')) + data = self.client_sock.recv(1024) + status_text = data.decode('utf-8') + self.update_status_label(status_text) + + except bluetooth.btcommon.BluetoothError as e: + self.is_connected = False + self.update_status_label(f"Connection Lost: {e}") + print(f"[DEBUG] 통신 오류: {e}. 재연결 시도...") + Clock.unschedule(self.get_system_status) + self.connect_to_server() + + def update_status_label(self, text): + self.status_label.text = text + if "Connected" in text or "CPU" in text: + self.status_label.color = (0, 1, 0, 1) # 녹색 + else: + self.status_label.color = (1, 0, 0, 1) # 빨간색 + + def send_command(self, command): + """UI에서 호출될 함수: 서버로 명령을 보냅니다.""" + if not self.is_connected or not self.client_sock: + self.update_status_label("Not connected. Cannot send command.") + print("Not connected to server.") + return + + try: + print(f"[DEBUG] '{command}' 명령 전송 시도...") + self.client_sock.send(command.encode('utf-8')) + data = self.client_sock.recv(1024) + response_text = data.decode('utf-8') + self.update_status_label(f"Response: {response_text}") + print(f"[DEBUG] 서버 응답: {response_text}") + + except bluetooth.btcommon.BluetoothError as e: + self.is_connected = False + self.update_status_label(f"Send failed: {e}") + print(f"[DEBUG] 전송 오류: {e}. 연결 끊김...") + Clock.unschedule(self.get_system_status) + self.connect_to_server() + + +# 버튼 위젯 클래스 (이전과 동일) +class IconButton(ButtonBehavior, BoxLayout): + def __init__(self, icon_path, cmd, app_instance, **kwargs): + super().__init__(orientation='vertical', **kwargs) + self.cmd = cmd + self.app_instance = app_instance + + with self.canvas.before: + Color(0.2, 0.2, 0.2, 1) + self.rect = RoundedRectangle(radius=[20], pos=self.pos, size=self.size) + + self.bind(pos=self.update_rect, size=self.update_rect) + self.add_widget(Image(source=icon_path, size_hint=(1, 1), allow_stretch=True, keep_ratio=False)) + + def update_rect(self, *args): + self.rect.pos = self.pos + self.rect.size = self.size + + def on_press(self): + print(f"[DEBUG] 버튼 클릭됨: {self.cmd}") + self.app_instance.send_command(self.cmd) + +if __name__ == "__main__": + DashboardApp().run() + # 버전 체크용 코드: 파일이 올바르게 복사되었는지 확인합니다. + # 이 숫자는 이 코드가 생성된 시점을 나타내는 고유 식별자입니다. + print(f"File version: 8207010") + diff --git a/rpiKivyDash/requirements.txt b/rpiKivyDash/requirements.txt new file mode 100644 index 0000000..5f439d5 --- /dev/null +++ b/rpiKivyDash/requirements.txt @@ -0,0 +1,11 @@ +certifi==2025.8.3 +charset-normalizer==3.4.3 +docutils==0.22 +filetype==1.2.0 +idna==3.10 +Kivy==2.3.1 +Kivy-Garden==0.1.5 +PyBluez @ git+https://github.com/pybluez/pybluez.git@82cbba8a1ebd4c1e3442dfafd8581d58c50fa39e +Pygments==2.19.2 +requests==2.32.5 +urllib3==2.5.0 diff --git a/rpiKivyDash/tests/ble_client.py b/rpiKivyDash/tests/ble_client.py new file mode 100644 index 0000000..9f95f3a --- /dev/null +++ b/rpiKivyDash/tests/ble_client.py @@ -0,0 +1,185 @@ +import json +import bluetooth +import threading +from kivy.config import Config +import time +import sys + +# Kivy 앱의 크기와 설정을 고정합니다. +# 풀스크린과 테두리 없음을 설정하여 라즈베리파이 LCD에 맞춰 실행되도록 합니다. +Config.set('graphics', 'fullscreen', '1') +Config.set('graphics', 'borderless', '1') +Config.set('graphics', 'resizable', '0') +Config.set('graphics', 'width', '480') +Config.set('graphics', 'height', '320') +Config.set('graphics', 'show_cursor', '0') + +# 서버의 블루투스 MAC 주소와 UUID를 설정합니다. +# 이 값은 갤럭시북의 정보와 일치해야 합니다. +SERVER_MAC = '8C:E9:EE:C9:33:4D' +PORT = 5 + + +from kivy.app import App +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.gridlayout import GridLayout +from kivy.uix.label import Label +from kivy.uix.image import Image +from kivy.uix.behaviors import ButtonBehavior +from kivy.core.window import Window +from kivy.graphics import Color, RoundedRectangle +from kivy.clock import Clock + + +# Kivy 애플리케이션 클래스 +class DashboardApp(App): + def build(self): + self.client_sock = None + self.is_connected = False + self.status_label = Label(text="Connecting...", font_size=20, color=(1, 1, 0, 1)) + + # Kivy UI 구성 + root = BoxLayout(orientation='vertical', padding=10) + + title = Label(text="MK's GalaxyBook5 Dashboard", font_size=25, size_hint_y=None, height=40) + root.add_widget(title) + + grid = GridLayout(cols=4, rows=2, spacing=10, padding=1) + + # cmder.json 파일에서 버튼 데이터 로드 + try: + with open("cmders.json", "r", encoding="utf-8") as f: + self.cmders = json.load(f) + except FileNotFoundError: + self.cmders = [] + print("cmders.json 파일을 찾을 수 없습니다. 기본값으로 실행합니다.") + + for item in self.cmders: + icon_path = item["icons_path"] if item["icons_path"] else "icons/default.png" + cmd = item["cmd"] + btn = IconButton(icon_path=icon_path, cmd=cmd, app_instance=self) + grid.add_widget(btn) + + root.add_widget(grid) + + self.status_label.text = "Attempting to connect..." + self.status_label.size_hint_y = None + self.status_label.height = 30 + root.add_widget(self.status_label) + + # 연결 시도 시작 + print("[DEBUG] 초기 연결 시도 시작...") + self.connect_scheduled_event = Clock.schedule_once(self.connect_to_server, 1) + + return root + + def connect_to_server(self, dt=0): + """서버에 연결을 시도하는 함수입니다.""" + if self.client_sock: + self.client_sock.close() + self.client_sock = None + + self.is_connected = False + self.update_status_label("Connecting...") + print(f"[DEBUG] 연결 시도: MAC={SERVER_MAC}, PORT={PORT}") + + try: + self.client_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) + # self.client_sock.settimeout(5.0) + self.client_sock.connect((SERVER_MAC, PORT)) + self.is_connected = True + self.update_status_label("Connected!") + print("[DEBUG] 연결 성공!") + + # 연결 성공 시 5초마다 상태 업데이트 스케줄링 + Clock.schedule_interval(self.get_system_status, 5) + + except bluetooth.btcommon.BluetoothError as e: + self.update_status_label(f"Connection Failed: {e}") + print(f"[DEBUG] 연결 실패: {e}. 5초 후 재시도...") + if self.client_sock: + self.client_sock.close() + self.client_sock = None + # 연결 실패 시 5초 후 재시도 + Clock.schedule_once(self.connect_to_server, 5) + + + def get_system_status(self, dt): + """서버에 시스템 상태를 요청하고 응답을 받습니다.""" + if not self.is_connected or not self.client_sock: + print("[DEBUG] 연결이 끊어져 상태 업데이트 중단.") + Clock.unschedule(self.get_system_status) + self.connect_to_server() + return + + try: + self.client_sock.send("get_status".encode('utf-8')) + data = self.client_sock.recv(1024) + status_text = data.decode('utf-8') + self.update_status_label(status_text) + + except bluetooth.btcommon.BluetoothError as e: + self.is_connected = False + self.update_status_label(f"Connection Lost: {e}") + print(f"[DEBUG] 통신 오류: {e}. 재연결 시도...") + Clock.unschedule(self.get_system_status) + self.connect_to_server() + + def update_status_label(self, text): + self.status_label.text = text + if "Connected" in text or "CPU" in text: + self.status_label.color = (0, 1, 0, 1) # 녹색 + else: + self.status_label.color = (1, 0, 0, 1) # 빨간색 + + def send_command(self, command): + """UI에서 호출될 함수: 서버로 명령을 보냅니다.""" + if not self.is_connected or not self.client_sock: + self.update_status_label("Not connected. Cannot send command.") + print("Not connected to server.") + return + + try: + print(f"[DEBUG] '{command}' 명령 전송 시도...") + self.client_sock.send(command.encode('utf-8')) + data = self.client_sock.recv(1024) + response_text = data.decode('utf-8') + self.update_status_label(f"Response: {response_text}") + print(f"[DEBUG] 서버 응답: {response_text}") + + except bluetooth.btcommon.BluetoothError as e: + self.is_connected = False + self.update_status_label(f"Send failed: {e}") + print(f"[DEBUG] 전송 오류: {e}. 연결 끊김...") + Clock.unschedule(self.get_system_status) + self.connect_to_server() + + +# 버튼 위젯 클래스 (이전과 동일) +class IconButton(ButtonBehavior, BoxLayout): + def __init__(self, icon_path, cmd, app_instance, **kwargs): + super().__init__(orientation='vertical', **kwargs) + self.cmd = cmd + self.app_instance = app_instance + + with self.canvas.before: + Color(0.2, 0.2, 0.2, 1) + self.rect = RoundedRectangle(radius=[20], pos=self.pos, size=self.size) + + self.bind(pos=self.update_rect, size=self.update_rect) + self.add_widget(Image(source=icon_path, size_hint=(1, 1), allow_stretch=True, keep_ratio=False)) + + def update_rect(self, *args): + self.rect.pos = self.pos + self.rect.size = self.size + + def on_press(self): + print(f"[DEBUG] 버튼 클릭됨: {self.cmd}") + self.app_instance.send_command(self.cmd) + +if __name__ == "__main__": + DashboardApp().run() + # 버전 체크용 코드: 파일이 올바르게 복사되었는지 확인합니다. + # 이 숫자는 이 코드가 생성된 시점을 나타내는 고유 식별자입니다. + print(f"File version: 8207010") + diff --git a/rpiKivyDash/tests/fullscreen.py b/rpiKivyDash/tests/fullscreen.py new file mode 100644 index 0000000..7b2b5b2 --- /dev/null +++ b/rpiKivyDash/tests/fullscreen.py @@ -0,0 +1,15 @@ +from kivy.config import Config +Config.set('graphics', 'fullscreen', 'auto') # 또는 '1' +Config.set('graphics', 'borderless', '1') +Config.set('graphics', 'resizable', '0') +Config.set('graphics', 'width', '480') # 라즈베리파이 해상도에 맞게 조정 +Config.set('graphics', 'height', '320') # 예: 800x480 LCD + +from kivy.app import App +from kivy.uix.button import Button + +class MyApp(App): + def build(self): + return Button(text='LAUNCH', font_size='40sp') + +MyApp().run() diff --git a/rpiKivyDash/tests/rf_client.py b/rpiKivyDash/tests/rf_client.py new file mode 100644 index 0000000..0c4c503 --- /dev/null +++ b/rpiKivyDash/tests/rf_client.py @@ -0,0 +1,20 @@ +import bluetooth +import time +server_mac = '8C:E9:EE:C9:33:4D' +port = 5 + +while True: + sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) + sock.connect((server_mac, port)) + + try: + # sock.send("get_status".encode()) + sock.send("get_status".encode('utf-8')) + data = sock.recv(1024) + print("서버 응답:", data.decode()) + except bluetooth.btcommon.BluetoothError as e: + print("Bluetooth 오류 발생:", e) + finally: + sock.close() + + time.sleep(2)