diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 399f698..fc248cd 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -4,4 +4,15 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
+
+
+
+
+
+
diff --git a/lib/home_screen_content.dart b/lib/home_screen_content.dart
index 97bac80..a89739d 100644
--- a/lib/home_screen_content.dart
+++ b/lib/home_screen_content.dart
@@ -4,6 +4,9 @@ import 'package:latlong2/latlong.dart';
import 'dart:ui' as ui;
import 'package:smarthelmet_app/widgets/custom_header.dart';
+//25.12.03 지은 추가
+import 'package:smarthelmet_app/services/locker_api.dart';
+
class HomeScreenContent extends StatefulWidget {
const HomeScreenContent({super.key});
@@ -23,6 +26,25 @@ class _HomeScreenContentState extends State {
final Color _toggleOffTrackColor = const Color(0xFFE0E0E0);
final Color _toggleOffKnobBorderColor = const Color(0xFFE0E0E0);
+// 25.12.03 지은 추가 시작
+ final LockerApi _api = LockerApi();
+ bool _isLoading = false;
+
+Future _runLockerAction(String name, Future Function() action) async {
+ if (_isLoading) return;
+
+ setState(() => _isLoading = true);
+
+ // 실제 명령 전송
+ final success = await action();
+
+ setState(() => _isLoading = false);
+
+ if (!mounted) return;
+ }
+
+ // 25.12.03 지은 추가 끝
+
int _selectedImageIndex = 0;
static const BoxShadow _cleanShadow = BoxShadow(
@@ -329,7 +351,21 @@ class _HomeScreenContentState extends State {
child: _buildStyledToggleSwitch(
'UV LED',
_controlToggles['UV LED']!,
- (val) => setState(() => _controlToggles['UV LED'] = val))),
+ // 25.12.03 지은 수정 시작
+ (val) {
+ _runLockerAction("UV 제어", () async {
+ bool success = await _api.setUV(val);
+
+ if (success) {
+ setState(() => _controlToggles['UV LED'] = val);
+ }
+ return success;
+ }
+ );
+ },
+ ),
+ ),
+ // 25.12.03 지은 수정 끝
VerticalDivider(color: _mainBlueColor.withOpacity(0.5), indent: 10, endIndent: 10),
Expanded(
child: _buildStyledToggleSwitch(
@@ -346,7 +382,22 @@ class _HomeScreenContentState extends State {
Expanded(
child: _buildStyledToggleSwitch('FAN',
_controlToggles['FAN']!,
- (val) => setState(() => _controlToggles['FAN'] = val))),
+ // 25.12.03 지은 수정 시작
+ (val) {
+ print("👉 [디버깅] fan 눌림! 값: $val");
+ _runLockerAction("FAN 제어", () async {
+ print("👉 [디버깅] API 요청 시작...");
+ bool success = await _api.setFan(val);
+
+ if (success) {
+ setState(() => _controlToggles['FAN'] = val);
+ }
+ return success;
+ });
+ },
+ // 25.12.03 지은 수정 끝
+ ),
+ ),
],
),
)
diff --git a/lib/rental_process_screen.dart b/lib/rental_process_screen.dart
index 18ac8ee..54fa613 100644
--- a/lib/rental_process_screen.dart
+++ b/lib/rental_process_screen.dart
@@ -3,6 +3,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
+//25.12.03 지은 추가
+import 'package:smarthelmet_app/services/locker_api.dart';
+
class RentalProcessScreen extends StatefulWidget {
final String stationName;
@@ -26,6 +29,25 @@ class _RentalProcessScreenState extends State {
static const Color _primaryGreen = Color(0xFF4CAF50);
static const Color _primaryOrange = Color(0xFFFF3D00);
+ // 25.12.03 지은 추가 시작
+ final LockerApi _api = LockerApi();
+ bool _isLoading = false;
+
+Future _runLockerAction(String name, Future Function() action) async {
+ if (_isLoading) return;
+
+ setState(() => _isLoading = true);
+
+ // 실제 명령 전송
+ final success = await action();
+
+ setState(() => _isLoading = false);
+
+ if (!mounted) return;
+ }
+
+ // 25.12.03 지은 추가 끝
+
final PageController _pageController = PageController();
int _currentPage = 0;
Timer? _timer;
@@ -241,6 +263,9 @@ class _RentalProcessScreenState extends State {
height: 68,
child: ElevatedButton(
onPressed: () {
+ // 25.12.03 지은 추가
+ _runLockerAction("잠금 해제", () => _api.unlock());
+
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('문이 열렸습니다! (OPEN 버튼 눌림)'),
diff --git a/lib/services/locker_api.dart b/lib/services/locker_api.dart
new file mode 100644
index 0000000..f8adae0
--- /dev/null
+++ b/lib/services/locker_api.dart
@@ -0,0 +1,59 @@
+import 'dart:convert';
+import 'package:http/http.dart' as http;
+
+class LockerApi {
+ // 안드로이드 에뮬레이터: "http://10.0.2.2:8182"
+ // iOS 시뮬레이터: "http://127.0.0.1:8182"
+ // "http://192.168.0.82:8182"
+ static const String baseUrl = "http://192.168.0.81:8182";
+
+ // 명령 전송 공통 함수
+ Future sendCommand({
+ required int addr,
+ required int value,
+ String target = "*"
+ }) async {
+ final url = Uri.parse('$baseUrl/modbus/write');
+
+ try {
+ final response = await http.post(
+ url,
+ headers: {"Content-Type": "application/json"},
+ body: jsonEncode({
+ "target": target,
+ "unit": 1,
+ "addr": addr,
+ "value": value
+ }),
+ );
+
+ if (response.statusCode == 200) {
+ print("명령 성공: Addr($addr) -> Val($value)");
+ return true;
+ } else {
+ print("명령 실패: ${response.body}");
+ return false;
+ }
+ } catch (e) {
+ print("서버 연결 오류: $e");
+ return false;
+ }
+ }
+
+ // --- [기능별 동작 함수] ---
+
+ // 1. 잠금 해제 (주소: 0x0016 / 값: 1)
+ Future unlock() async {
+ return await sendCommand(addr: 0x0016, value: 1);
+ }
+
+ // 2. 살균 (UV) 켜기/끄기 (주소: 0x0014 / 값: 1 or 0)
+ Future setUV(bool isOn) async {
+ return await sendCommand(addr: 0x0014, value: isOn ? 1 : 0);
+ }
+
+ // 3. 건조 (FAN) 켜기/끄기 (주소: 0x0015 / 값: 1 or 0)
+ Future setFan(bool isOn) async {
+ return await sendCommand(addr: 0x0015, value: isOn ? 1 : 0);
+ }
+}
\ No newline at end of file