Управление умным домом на kotlin

от автора

Кратко

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *