diff --git a/Source Code Mobile/ac_model.dart b/Source Code Mobile/ac_model.dart new file mode 100644 index 0000000..b51f21c --- /dev/null +++ b/Source Code Mobile/ac_model.dart @@ -0,0 +1,57 @@ +class ACModel { + final bool power; + final String mode; + final int temp; + final String fan; + final bool swing; + final String controlMode; + + ACModel({ + required this.power, + required this.mode, + required this.temp, + required this.fan, + required this.swing, + required this.controlMode, + }); + + factory ACModel.fromMap(Map json) { + return ACModel( + power: json['power'] ?? false, + mode: json['mode']?.toString().toUpperCase() ?? 'COOL', + temp: json['temp'] ?? 24, + fan: json['fan']?.toString().toUpperCase() ?? 'AUTO', + swing: json['swing'] == true, + controlMode: json['control_mode']?.toString().toLowerCase() ?? 'auto', + ); + } + + Map toMap() { + return { + 'power': power, + 'mode': mode, + 'temp': temp, + 'fan': fan, + 'swing': swing, + 'control_mode': controlMode, + }; + } + + ACModel copyWith({ + bool? power, + String? mode, + int? temp, + String? fan, + bool? swing, + String? controlMode, + }) { + return ACModel( + power: power ?? this.power, + mode: mode ?? this.mode, + temp: temp ?? this.temp, + fan: fan ?? this.fan, + swing: swing ?? this.swing, + controlMode: controlMode ?? this.controlMode, + ); + } +} diff --git a/Source Code Mobile/ac_protocol_model.dart b/Source Code Mobile/ac_protocol_model.dart new file mode 100644 index 0000000..63864f6 --- /dev/null +++ b/Source Code Mobile/ac_protocol_model.dart @@ -0,0 +1,626 @@ +class ACProtocol { + final int id; + final String protocolName; + final String displayName; + final String description; + final bool hasModel; + final List modelNames; + + ACProtocol({ + required this.id, + required this.protocolName, + required this.displayName, + required this.description, + required this.hasModel, + required this.modelNames, + }); +} + +// Data dari ESP32 Script - 67 Protocol AC +final List acProtocols = [ + ACProtocol( + id: 0, + protocolName: 'PANASONIC_AC', + displayName: 'Panasonic', + description: 'Panasonic AC', + hasModel: true, + modelNames: ['LKE', 'NKE', 'DKE', 'JKE', 'CKP', 'RKR'], + ), + ACProtocol( + id: 1, + protocolName: 'FUJITSU_AC', + displayName: 'Fujitsu', + description: 'Fujitsu AC', + hasModel: true, + modelNames: ['ARDB1', 'ARRAH2E', 'ARREB1E', 'ARJW2', 'ARRY4', 'ARREW4E'], + ), + ACProtocol( + id: 2, + protocolName: 'DAIKIN', + displayName: 'Daikin', + description: 'Daikin AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 3, + protocolName: 'DAIKIN2', + displayName: 'Daikin 2', + description: 'Daikin 2 AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 4, + protocolName: 'DAIKIN64', + displayName: 'Daikin 64-bit', + description: 'Daikin 64-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 5, + protocolName: 'DAIKIN128', + displayName: 'Daikin 128-bit', + description: 'Daikin 128-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 6, + protocolName: 'DAIKIN152', + displayName: 'Daikin 152-bit', + description: 'Daikin 152-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 7, + protocolName: 'DAIKIN160', + displayName: 'Daikin 160-bit', + description: 'Daikin 160-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 8, + protocolName: 'DAIKIN176', + displayName: 'Daikin 176-bit', + description: 'Daikin 176-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 9, + protocolName: 'DAIKIN200', + displayName: 'Daikin 200-bit', + description: 'Daikin 200-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 10, + protocolName: 'DAIKIN216', + displayName: 'Daikin 216-bit', + description: 'Daikin 216-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 11, + protocolName: 'DAIKIN312', + displayName: 'Daikin 312-bit', + description: 'Daikin 312-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 12, + protocolName: 'MITSUBISHI_AC', + displayName: 'Mitsubishi', + description: 'Mitsubishi Electric AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 13, + protocolName: 'MITSUBISHI112', + displayName: 'Mitsubishi 112-bit', + description: 'Mitsubishi 112-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 14, + protocolName: 'MITSUBISHI136', + displayName: 'Mitsubishi 136-bit', + description: 'Mitsubishi 136-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 15, + protocolName: 'MITSUBISHI_HEAVY_88', + displayName: 'Mitsubishi Heavy 88-bit', + description: 'Mitsubishi Heavy 88-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 16, + protocolName: 'MITSUBISHI_HEAVY_152', + displayName: 'Mitsubishi Heavy 152-bit', + description: 'Mitsubishi Heavy 152-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 17, + protocolName: 'HITACHI_AC', + displayName: 'Hitachi', + description: 'Hitachi AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 18, + protocolName: 'HITACHI_AC1', + displayName: 'Hitachi AC1', + description: 'Hitachi AC1', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 19, + protocolName: 'HITACHI_AC264', + displayName: 'Hitachi AC264', + description: 'Hitachi AC264', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 20, + protocolName: 'HITACHI_AC296', + displayName: 'Hitachi AC296', + description: 'Hitachi AC296', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 21, + protocolName: 'HITACHI_AC344', + displayName: 'Hitachi AC344', + description: 'Hitachi AC344', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 22, + protocolName: 'HITACHI_AC424', + displayName: 'Hitachi AC424', + description: 'Hitachi AC424', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 23, + protocolName: 'LG', + displayName: 'LG', + description: 'LG AC', + hasModel: true, + modelNames: [ + 'GE6711AR2853M', + 'LG6711A20083V', + 'AKB74955603', + 'AKB73757604', + ], + ), + ACProtocol( + id: 24, + protocolName: 'LG2', + displayName: 'LG 2', + description: 'LG2 AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 25, + protocolName: 'SAMSUNG_AC', + displayName: 'Samsung', + description: 'Samsung AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 26, + protocolName: 'SAMSUNG36', + displayName: 'Samsung 36-bit', + description: 'Samsung 36-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 27, + protocolName: 'SHARP_AC', + displayName: 'Sharp', + description: 'Sharp AC', + hasModel: true, + modelNames: ['A705', 'A903', 'A907'], + ), + ACProtocol( + id: 28, + protocolName: 'GREE', + displayName: 'Gree', + description: 'Gree AC', + hasModel: true, + modelNames: ['YAW1F', 'YBOFB', 'YX1FSF'], + ), + ACProtocol( + id: 29, + protocolName: 'KELVINATOR', + displayName: 'Kelvinator', + description: 'Kelvinator AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 30, + protocolName: 'COOLIX', + displayName: 'Coolix/Midea', + description: 'Coolix/Midea AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 31, + protocolName: 'COOLIX48', + displayName: 'Coolix 48-bit', + description: 'Coolix 48-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 32, + protocolName: 'MIDEA', + displayName: 'Midea', + description: 'Midea AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 33, + protocolName: 'MIDEA24', + displayName: 'Midea 24-bit', + description: 'Midea 24-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 34, + protocolName: 'WHIRLPOOL_AC', + displayName: 'Whirlpool', + description: 'Whirlpool AC', + hasModel: true, + modelNames: ['DG11J13A', 'DG11J191'], + ), + ACProtocol( + id: 35, + protocolName: 'HAIER_AC', + displayName: 'Haier', + description: 'Haier AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 36, + protocolName: 'HAIER_AC_YRW02', + displayName: 'Haier YRW02', + description: 'Haier YRW02 AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 37, + protocolName: 'HAIER_AC160', + displayName: 'Haier 160-bit', + description: 'Haier 160-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 38, + protocolName: 'HAIER_AC176', + displayName: 'Haier 176-bit', + description: 'Haier 176-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 39, + protocolName: 'CARRIER_AC', + displayName: 'Carrier', + description: 'Carrier AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 40, + protocolName: 'CARRIER_AC84', + displayName: 'Carrier 84-bit', + description: 'Carrier 84-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 41, + protocolName: 'CARRIER_AC128', + displayName: 'Carrier 128-bit', + description: 'Carrier 128-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 42, + protocolName: 'TCL112AC', + displayName: 'TCL 112-bit', + description: 'TCL 112-bit AC', + hasModel: true, + modelNames: ['TAC09CHSD', 'GZ055BE1'], + ), + ACProtocol( + id: 43, + protocolName: 'TCL96AC', + displayName: 'TCL 96-bit', + description: 'TCL 96-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 44, + protocolName: 'SANYO_AC', + displayName: 'Sanyo', + description: 'Sanyo AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 45, + protocolName: 'SANYO_AC152', + displayName: 'Sanyo 152-bit', + description: 'Sanyo 152-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 46, + protocolName: 'TOSHIBA_AC', + displayName: 'Toshiba', + description: 'Toshiba AC', + hasModel: true, + modelNames: ['GENERICREMOTE_A', 'GENERICREMOTE_B'], + ), + ACProtocol( + id: 47, + protocolName: 'ELECTRA_AC', + displayName: 'Electra/AUX/Frigidaire', + description: 'Electra/AUX/Frigidaire AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 48, + protocolName: 'NEOCLIMA', + displayName: 'Neoclima', + description: 'Neoclima AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 49, + protocolName: 'VOLTAS', + displayName: 'Voltas', + description: 'Voltas AC', + hasModel: true, + modelNames: ['122LZF'], + ), + ACProtocol( + id: 50, + protocolName: 'VESTEL_AC', + displayName: 'Vestel', + description: 'Vestel AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 51, + protocolName: 'TROTEC', + displayName: 'Trotec', + description: 'Trotec AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 52, + protocolName: 'TROTEC_3550', + displayName: 'Trotec 3550', + description: 'Trotec 3550 AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 53, + protocolName: 'AMCOR', + displayName: 'Amcor', + description: 'Amcor AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 54, + protocolName: 'ARGO', + displayName: 'Argo', + description: 'Argo AC', + hasModel: true, + modelNames: ['SAC_WREM2', 'SAC_WREM3'], + ), + ACProtocol( + id: 55, + protocolName: 'CORONA_AC', + displayName: 'Corona', + description: 'Corona AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 56, + protocolName: 'DELONGHI_AC', + displayName: 'DeLonghi', + description: 'DeLonghi AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 57, + protocolName: 'ECOCLIM', + displayName: 'EcoClim', + description: 'EcoClim AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 58, + protocolName: 'EUROM', + displayName: 'Eurom', + description: 'Eurom AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 59, + protocolName: 'GOODWEATHER', + displayName: 'Goodweather', + description: 'Goodweather AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 60, + protocolName: 'KELON', + displayName: 'Kelon/Hisense', + description: 'Kelon/Hisense AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 61, + protocolName: 'KELON168', + displayName: 'Kelon 168-bit', + description: 'Kelon 168-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 62, + protocolName: 'MIRAGE', + displayName: 'Mirage', + description: 'Mirage AC', + hasModel: true, + modelNames: ['KKG9AC1', 'KKG29AC1'], + ), + ACProtocol( + id: 63, + protocolName: 'RHOSS', + displayName: 'Rhoss', + description: 'Rhoss AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 64, + protocolName: 'TEKNOPOINT', + displayName: 'Teknopoint', + description: 'Teknopoint AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 65, + protocolName: 'TRANSCOLD', + displayName: 'Transcold', + description: 'Transcold AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 66, + protocolName: 'TRUMA', + displayName: 'Truma', + description: 'Truma AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 67, + protocolName: 'YORK', + displayName: 'York', + description: 'York AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 68, + protocolName: 'AIRTON', + displayName: 'Airton', + description: 'Airton AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 69, + protocolName: 'AIRWELL', + displayName: 'Airwell', + description: 'Airwell AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 70, + protocolName: 'CLIMABUTLER', + displayName: 'Clima-Butler', + description: 'Clima-Butler AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 71, + protocolName: 'TECO', + displayName: 'Teco/Alaska', + description: 'Teco/Alaska AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 72, + protocolName: 'WHYNTER', + displayName: 'Whynter', + description: 'Whynter AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 73, + protocolName: 'BOSCH144', + displayName: 'Bosch 144-bit', + description: 'Bosch 144-bit AC', + hasModel: false, + modelNames: [], + ), + ACProtocol( + id: 74, + protocolName: 'TECHNIBEL_AC', + displayName: 'Technibel', + description: 'Technibel AC', + hasModel: false, + modelNames: [], + ), +]; diff --git a/Source Code Mobile/control_history_model.dart b/Source Code Mobile/control_history_model.dart new file mode 100644 index 0000000..e597c3d --- /dev/null +++ b/Source Code Mobile/control_history_model.dart @@ -0,0 +1,42 @@ +class ControlHistoryModel { + final String action; + final String source; + final String timestamp; + final int rawTimestamp; + + ControlHistoryModel({ + required this.action, + required this.source, + required this.timestamp, + required this.rawTimestamp, + }); + + factory ControlHistoryModel.fromMap(Map json) { + final int ts = json['timestamp'] is int ? json['timestamp'] : 0; + return ControlHistoryModel( + action: json['action'] ?? '', + source: json['source'] ?? '', + timestamp: _parseTimestamp(json['timestamp']), + rawTimestamp: ts, + ); + } + + Map toMap() { + return { + 'action': action, + 'source': source, + 'timestamp': timestamp, + 'rawTimestamp': rawTimestamp, + }; + } +} + +String _parseTimestamp(dynamic value) { + if (value == null) return ''; + if (value is int) { + final dt = DateTime.fromMillisecondsSinceEpoch(value * 1000); + return "${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} " + "${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}"; + } + return value.toString(); +} diff --git a/Source Code Mobile/history_model.dart b/Source Code Mobile/history_model.dart new file mode 100644 index 0000000..d5d7389 --- /dev/null +++ b/Source Code Mobile/history_model.dart @@ -0,0 +1,60 @@ +class HistoryModel { + final double temperature; + final double humidity; + final double co2; + final double pm25; + final String overallStatus; + final String timestamp; + // ac_action sudah dihapus dari script ESP32 terbaru, gunakan default '-' + final String acAction; + + HistoryModel({ + required this.temperature, + required this.humidity, + required this.co2, + required this.pm25, + required this.overallStatus, + required this.timestamp, + this.acAction = '-', + }); + + factory HistoryModel.fromMap(Map json) { + return HistoryModel( + // Support both ESP32 field names and Flutter field names + temperature: ((json['temperature'] ?? json['suhu']) ?? 0).toDouble(), + humidity: ((json['humidity'] ?? json['kelembapan']) ?? 0).toDouble(), + co2: ((json['co2'] ?? json['co2_ppm']) ?? 0).toDouble(), + pm25: ((json['pm25'] ?? json['pm25_ugm3']) ?? 0).toDouble(), + overallStatus: + (json['overall_status'] ?? json['status_keseluruhan'] ?? ''), + timestamp: _parseTimestamp(json['timestamp']), + // Backward compatible: gunakan ac_action jika ada, jika tidak gunakan '-' + acAction: json['ac_action'] ?? '-', + ); + } + + Map toMap() { + return { + 'temperature': temperature, + 'humidity': humidity, + 'co2': co2, + 'pm25': pm25, + 'overall_status': overallStatus, + 'timestamp': timestamp, + 'ac_action': acAction, + }; + } +} + +String _parseTimestamp(dynamic value) { + if (value == null) return ''; + if (value is int) { + // ESP32 mengirim unix timestamp dalam detik, konversi ke milliseconds + // Jika value > 10000000000 berarti sudah dalam milliseconds + final timestampMs = value > 10000000000 ? value : value * 1000; + final dt = DateTime.fromMillisecondsSinceEpoch(timestampMs); + return "${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} " + "${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}"; + } + return value.toString(); +} diff --git a/Source Code Mobile/ir_received_model.dart b/Source Code Mobile/ir_received_model.dart new file mode 100644 index 0000000..fffe299 --- /dev/null +++ b/Source Code Mobile/ir_received_model.dart @@ -0,0 +1,32 @@ +class IrReceivedModel { + final String protocol; + final String code; + final int bits; + final String timestamp; + + IrReceivedModel({ + required this.protocol, + required this.code, + required this.bits, + required this.timestamp, + }); + + factory IrReceivedModel.fromMap(Map json) { + return IrReceivedModel( + protocol: json['protocol'] ?? 'UNKNOWN', + code: json['code'] ?? '0x0', + bits: json['bits'] ?? 0, + timestamp: _parseTimestamp(json['timestamp']), + ); + } +} + +String _parseTimestamp(dynamic value) { + if (value == null) return ''; + if (value is int) { + final dt = DateTime.fromMillisecondsSinceEpoch(value * 1000); + return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ' + '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}'; + } + return value.toString(); +}