Кратко
Smart System это небольшое и простое в использовании приложение для умного дома с простой структурой, написанное на kotlin.
Цель этого приложения — сделать удаленное выполнение функций максимально простым и удобным для пользователя, чтобы помочь начать работу с технологией умного дома.
Технологический стек самый стандартный — kotlin(backend), xml(frontend).
Поддерживаемые устройства
Smart System поддерживает следующие девайсы:
-
Philips Hue Bridge
-
Shelly
-
Devices using ESP Easy
-
Devices using Tasmota
-
Devices using the SimpleHome API
Activities
Главный экран содержит меню управления и подключения к устройствам умного дома.
В настройках есть возможность изменить тему приложения, возможность изменить расположение списка объектов, кнопка изменения списка объектов и удаление всех сразу.
Экран добавления объекта содержит имя , адрес объекта , выбор типа объекта и иконки и выбор модулей.
Скриншоты


Backend
Я остановлюсь наверное на самых важных и интересных частях кода — это RETROFIT. Как я писал в самом начале, мое приложение работает с определенными модулями(ESP, Hue, Shelly, SimpleHome, Tasmota). Поэтому в пакете resources находятся JSON объекты этих модулей. На примере ESPeasy, я покажу суть всей работы моего приложения с API.ё
В файле EspEasyAPI.kt и EspEasyAPIParser.kt (Думаю эти незамысловатые названия говорят сами за себя)
class EspEasyAPI( c: Context, deviceId: String, recyclerViewInterface: HomeRecyclerViewHelperInterface? ) : UnifiedAPI(c, deviceId, recyclerViewInterface) { private val parser = EspEasyAPIParser(c.resources, this) override fun loadList(callback: CallbackInterface) { val jsonObjectRequest = JsonObjectRequest( Request.Method.GET, url + "json", null, { infoResponse -> callback.onItemsLoaded( UnifiedRequestCallback( parser.parseResponse(infoResponse), deviceId ), recyclerViewInterface ) }, { error -> callback.onItemsLoaded( UnifiedRequestCallback(null, deviceId, Global.volleyError(c, error) ), null) } ) queue.add(jsonObjectRequest) } override fun loadStates(callback: RealTimeStatesCallback, offset: Int) { val jsonObjectRequest = JsonObjectRequest( Request.Method.GET, url + "json", null, { infoResponse -> callback.onStatesLoaded( parser.parseStates(infoResponse), offset, dynamicSummaries ) }, { } ) queue.add(jsonObjectRequest) } override fun changeSwitchState(id: String, state: Boolean) { val switchUrl = url + "control?cmd=GPIO," + id + "," + (if (state) "1" else "0") val jsonObjectRequest = JsonObjectRequest( switchUrl, { }, { e -> Log.e(Global.LOG_TAG, e.toString()) } ) queue.add(jsonObjectRequest) } }
class EspEasyAPIParser(resources: Resources, api: UnifiedAPI?) : UnifiedAPI.Parser(resources, api) { override fun parseResponse(response: JSONObject): ArrayList<ListViewItem> { val listItems = arrayListOf<ListViewItem>() //sensors val sensors = response.optJSONArray("Sensors") ?: JSONArray() for (sensorId in 0 until sensors.length()) { val currentSensor = sensors.getJSONObject(sensorId) if (currentSensor.optString("TaskEnabled", "false").equals("false")) { continue } val type = currentSensor.optString("Type") if (type.startsWith("Environment")) { parseEnvironment(listItems, type, currentSensor) } else if (type.startsWith("Switch")) { parseSwitch(listItems, type, currentSensor) } } return listItems } private fun parseEnvironment(listItems: ArrayList<ListViewItem>, type: String, currentSensor: JSONObject) { var taskIcons = intArrayOf() when (type) { "Environment - BMx280" -> { taskIcons += R.drawable.ic_device_thermometer taskIcons += R.drawable.ic_device_hygrometer taskIcons += R.drawable.ic_device_gauge } "Environment - DHT11/12/22 SONOFF2301/7021" -> { taskIcons += R.drawable.ic_device_thermometer taskIcons += R.drawable.ic_device_hygrometer } "Environment - DS18b20" -> { taskIcons += R.drawable.ic_device_thermometer } } val taskName = currentSensor.getString("TaskName") for (taskId in taskIcons.indices) { val currentTask = currentSensor.getJSONArray("TaskValues").getJSONObject(taskId) val currentValue = currentTask.getString("Value") if (!currentValue.equals("nan")) { val suffix = when (taskIcons[taskId]) { R.drawable.ic_device_thermometer -> " °C" R.drawable.ic_device_hygrometer -> " %" R.drawable.ic_device_gauge -> " hPa" else -> "" } listItems += ListViewItem( title = currentValue + suffix, summary = taskName + ": " + currentTask.getString("Name"), icon = taskIcons[taskId] ) } } } private fun parseSwitch(listItems: ArrayList<ListViewItem>, type: String, currentSensor: JSONObject) { when (type) { "Switch input - Switch" -> { val currentState = currentSensor.getJSONArray("TaskValues").getJSONObject(0).getInt("Value") > 0 var taskName =currentSensor.getString("TaskName") var gpioId = "" val gpioFinder = Regex("~GPIO~([0-9]+)$") val matchResult = gpioFinder.find(taskName) if (matchResult != null && matchResult.groupValues.size > 1) { gpioId = matchResult.groupValues[1] taskName = taskName.replace("~GPIO~$gpioId", "") } listItems += ListViewItem( title = taskName, summary = resources.getString( if (currentState) R.string.switch_summary_on else R.string.switch_summary_off ), hidden = gpioId, state = currentState, icon = R.drawable.ic_do ) api?.needsRealTimeData = true } } } override fun parseStates(response: JSONObject): ArrayList<Boolean?> { val listItems = arrayListOf<Boolean?>() //sensors val sensors = response.optJSONArray("Sensors") ?: JSONArray() for (sensorId in 0 until sensors.length()) { val currentSensor = sensors.getJSONObject(sensorId) if (currentSensor.optString("TaskEnabled", "false").equals("false")) { continue } val type = currentSensor.optString("Type") if (type.startsWith("Environment")) { parseEnvironmentStates(listItems, type, currentSensor) } else if (type.startsWith("Switch")) { parseSwitchStates(listItems, type, currentSensor) } } return listItems } private fun parseEnvironmentStates(listItems: ArrayList<Boolean?>, type: String, currentSensor: JSONObject) { var tasks = 0 when (type) { "Environment - BMx280" -> tasks += 3 "Environment - DHT11/12/22 SONOFF2301/7021" -> tasks += 2 "Environment - DS18b20" -> tasks++ } for (taskId in 0 until tasks) { if ( !currentSensor.getJSONArray("TaskValues") .getJSONObject(taskId) .getString("Value") .equals("nan") ) listItems += null } } private fun parseSwitchStates(listItems: ArrayList<Boolean?>, type: String, currentSensor: JSONObject) { when (type) { "Switch input - Switch" -> { listItems += currentSensor.getJSONArray("TaskValues").getJSONObject(0).getInt("Value") > 0 } } } }
Если кому-то интересно как я реализовал остальные аспекты работы приложения на kotlin, то можно ознакомиться с кодом подробнее по ссылке ниже.
Приложение было разработано за два дня в рамках хакатона be-coder с тематикой Системы умного дома. Некоторые части кода возможно были выполнены небрежно и требуют доработки, так что буду рад выслушать советы и конструктивную критику!)
ссылка на оригинал статьи https://habr.com/ru/post/664312/
Добавить комментарий