25.12.04 잠금 해제, uv 살균, 건조팬 동작 기능 구현
This commit is contained in:
@@ -4,4 +4,15 @@
|
|||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|
||||||
|
<!-- 25.12.03 지은 추가 시작 -->
|
||||||
|
<application
|
||||||
|
android:label="smarthelmet_app"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
>
|
||||||
|
</application>
|
||||||
|
<!-- 25.12.03 지은 추가 끝 -->
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import 'package:latlong2/latlong.dart';
|
|||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
import 'package:smarthelmet_app/widgets/custom_header.dart';
|
import 'package:smarthelmet_app/widgets/custom_header.dart';
|
||||||
|
|
||||||
|
//25.12.03 지은 추가
|
||||||
|
import 'package:smarthelmet_app/services/locker_api.dart';
|
||||||
|
|
||||||
class HomeScreenContent extends StatefulWidget {
|
class HomeScreenContent extends StatefulWidget {
|
||||||
const HomeScreenContent({super.key});
|
const HomeScreenContent({super.key});
|
||||||
|
|
||||||
@@ -23,6 +26,25 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
final Color _toggleOffTrackColor = const Color(0xFFE0E0E0);
|
final Color _toggleOffTrackColor = const Color(0xFFE0E0E0);
|
||||||
final Color _toggleOffKnobBorderColor = const Color(0xFFE0E0E0);
|
final Color _toggleOffKnobBorderColor = const Color(0xFFE0E0E0);
|
||||||
|
|
||||||
|
// 25.12.03 지은 추가 시작
|
||||||
|
final LockerApi _api = LockerApi();
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
Future<void> _runLockerAction(String name, Future<bool> 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;
|
int _selectedImageIndex = 0;
|
||||||
|
|
||||||
static const BoxShadow _cleanShadow = BoxShadow(
|
static const BoxShadow _cleanShadow = BoxShadow(
|
||||||
@@ -329,7 +351,21 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
child: _buildStyledToggleSwitch(
|
child: _buildStyledToggleSwitch(
|
||||||
'UV LED',
|
'UV LED',
|
||||||
_controlToggles['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),
|
VerticalDivider(color: _mainBlueColor.withOpacity(0.5), indent: 10, endIndent: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStyledToggleSwitch(
|
child: _buildStyledToggleSwitch(
|
||||||
@@ -346,7 +382,22 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStyledToggleSwitch('FAN',
|
child: _buildStyledToggleSwitch('FAN',
|
||||||
_controlToggles['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 지은 수정 끝
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
|
//25.12.03 지은 추가
|
||||||
|
import 'package:smarthelmet_app/services/locker_api.dart';
|
||||||
|
|
||||||
class RentalProcessScreen extends StatefulWidget {
|
class RentalProcessScreen extends StatefulWidget {
|
||||||
final String stationName;
|
final String stationName;
|
||||||
|
|
||||||
@@ -26,6 +29,25 @@ class _RentalProcessScreenState extends State<RentalProcessScreen> {
|
|||||||
static const Color _primaryGreen = Color(0xFF4CAF50);
|
static const Color _primaryGreen = Color(0xFF4CAF50);
|
||||||
static const Color _primaryOrange = Color(0xFFFF3D00);
|
static const Color _primaryOrange = Color(0xFFFF3D00);
|
||||||
|
|
||||||
|
// 25.12.03 지은 추가 시작
|
||||||
|
final LockerApi _api = LockerApi();
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
Future<void> _runLockerAction(String name, Future<bool> 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();
|
final PageController _pageController = PageController();
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
@@ -241,6 +263,9 @@ class _RentalProcessScreenState extends State<RentalProcessScreen> {
|
|||||||
height: 68,
|
height: 68,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
// 25.12.03 지은 추가
|
||||||
|
_runLockerAction("잠금 해제", () => _api.unlock());
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('문이 열렸습니다! (OPEN 버튼 눌림)'),
|
content: Text('문이 열렸습니다! (OPEN 버튼 눌림)'),
|
||||||
|
|||||||
59
lib/services/locker_api.dart
Normal file
59
lib/services/locker_api.dart
Normal file
@@ -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<bool> 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<bool> unlock() async {
|
||||||
|
return await sendCommand(addr: 0x0016, value: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 살균 (UV) 켜기/끄기 (주소: 0x0014 / 값: 1 or 0)
|
||||||
|
Future<bool> setUV(bool isOn) async {
|
||||||
|
return await sendCommand(addr: 0x0014, value: isOn ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 건조 (FAN) 켜기/끄기 (주소: 0x0015 / 값: 1 or 0)
|
||||||
|
Future<bool> setFan(bool isOn) async {
|
||||||
|
return await sendCommand(addr: 0x0015, value: isOn ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user