MYCSS

2020-04-05

Погодна інтернет станція з старого планшета та панель розумного дому MQTT + OpenWRT + OpenWeather + Радіація


Ось є час під час домашнього карантину.
Відновив старий 7" планшет Impression ImPAD 1213 на основі процесора Allwinner A13 з Android 4.0.4, перепрошивши прошивку від виробника.
Але що з ним можна зробити ? Він дуже "тугий", але Google Play Market працює.
Тому встановив програму MQTT Dash (IoT, Smart Home), і вирішив реалізувати простішу панель для показу поточних даних погоди.
Хоча простіше можна було б показати готовий віджет від відомих погодних сайтів на головному екрані планшета.

Віджет від відомих погодних сайтів
Але головна мета розібратися з керування через MQTT пристроями в розумному домі. Тому іду іншим шляхом.

OpenWeather

Для отримання даних вирішив використати ресурс OpenWeather. Для використання API функціоналу потрібно мати персональний appid, котрий можна отримати після реєстрації користувача. Користувач може вибрати різні тарифні плани у тому числі і "Free".
За своїм тарифним планом "Free", я вибрав данні: Current weather data ,
Приклад запиту за ID мого міста, можна завантажити і знайти свій ID: city ID.
By city ID:
api.openweathermap.org/data/2.5/weather?id={city id}&appid={your api key}
Parameters:
id City ID
Examples of API calls:
api.openweathermap.org/data/2.5/weather?id=706483&appid={your api key}&units=metric&lang=ua
Результат є у форматі JSON:
{
  "coord": {
    "lon": 36.25,
    "lat": 50
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "чисте небо",
      "icon": "01n"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 5.22,
    "feels_like": -1.2,
    "temp_min": 5.22,
    "temp_max": 5.22,
    "pressure": 1030,
    "humidity": 58,
    "sea_level": 1030,
    "grnd_level": 1011
  },
  "wind": {
    "speed": 5.88,
    "deg": 339
  },
  "clouds": {
    "all": 0
  },
  "dt": 1586109523,
  "sys": {
    "country": "UA",
    "sunrise": 1586055742,
    "sunset": 1586103149
  },
  "timezone": 10800,
  "id": 706483,
  "name": "Kharkiv",
  "cod": 200
}

Візуальний результат у форматі JSON з OpenWeather

Брокер MQTT - mosquitto.

Пристрої розумного дому обмінються за протоколом MQTT:
MQTT (англ. Message Queue Telemetry Transport) — спрощений мережевий протокол, що працює на TCP/IP. Використовується для обміну повідомленнями між пристроями за принципом видавець-підписник.

Для використання MQTT необхідно мати прграмму брокер, вона приймає повідомлення від одного пристрою і розсилає це повідомлення всім іншим підписникам брокеру.

Для розміщення програми я вибираю свій домашній роутер, що працює на основі OpenWrt. Серед готових пакунків для роутера вибираю відому програму mosquitto.
opkg update
opkg list | egrep ^mosquitto-
opkg install mosquitto-nossl
opkg install mosquitto-client-nossl 
OpenWrt - mosquitto
Налаштування mosquitto можна знайти в мережі. У мене зараз мінімальні налаштування сервера без ssl, з використанням авторизаціїї за користувачами.
Для передачі данних на mosquitto сервер, використовується mosquitto_pub.
Для розподілення різних данних один від одного на сервері використовуть message-topic.
Для пердачі даних з погодного серверу OpenWeather я використовую  OpenWrt, на якому встанвленно і сервер mosquitto з топіком : wrt/temp-inet.
Приклад надсилання простого повідомлення з даними вологості:
mosquitto_pub -h 127.0.0.1 -u userwrt -P password81 -t wrt/temp-inet -m '58%'
Приклад надсилання повідомлення з зображенням використовуючи файл:
mosquitto_pub -h 127.0.0.1 -u userwrt -P password81 -t wrt/temp-inet -f 'image.png' 
Приклад надсилання повідомлення з потоку іншої програми:
echo test123 | mosquitto_pub -h 127.0.0.1 -u userwrt -P password81 -t wrt/temp-inet -s

Для автоматизації процесу, результат з OpenWeather отримую програмою wget , параметри q -O - дозволять видавати результат до потоку іншої програми.
Ось скрипт для автоматизації процесу для надсилання кожні 10 хв.
#!/bin/sh
appid=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
cityid=706483
topic=wrt/temp-inet
user=user1
passw=changeme1
wget -q -O - "https://api.openweathermap.org/data/2.5/weather?id=${cityid}&appid=${appid}&units=metric&lang=ua"| \
 mosquitto_pub -h 127.0.0.1 -u ${user} -P ${passw} -t ${topic} -s -q 1 -r

Таким чином кожні 10 хв, данні передаються до  MQTT брокера в топік wrt/temp-inet.
Якщо в сервері mosquitto вимкнено опіцію allow_duplicate_messages false, то до інших клієнтів котрі підписанні до сервера будуть надсилатися тільки данні у випадку коли вони змінилися.

MQTT Dash

Для візуалізації на планшеті встановлено програму кліент : MQTT Dash. котра підписується на топік брокера для отримання повідомлень.

В програмі MQTT Dash додаються об'єкти різних типів:
Об'єкти MQTT Dash
Для виділення об'єктів з JSON відповіді, використовується JSON Path expressions. Тестувати стовренні запити можна тут: https://jsonpath.com.
Вологість (main.humidity)
Температура (main.temp)
Атмосферний тиск (main.pressure)
Опис (weather[0].description)
Вітер (wind.speed)
Попередження про великий вітер у вигляді іконки котра блима можна задати так:
Блимання при значенні вітру більше 10 м/с
Зображення іконки з погодою (weather[0].icon)
Але в даних не передається посилання на саму іконку, а тільки її код, тому потрібно сформувати код завантаження іконки з сайту в форматі: http://openweathermap.org/img/wn/{icon}@2x.png

Обробка даних при показі даних за допомогою JavaSctipt
Обробка коду у JavaSctipt onDisplay event

Для простоти налаштування я робив налаштування в своєму мобільному телефоні, а потім налаштування публікував у топік metrics/exchange засобами програми.

Export / Import налаштувань через metrics/exchange
А на планшеті прийняв налаштування.

В результаті отримав налаштований планшет.
Планшет з погодними даними

Споживання енергії 

Вимкнений екран, 1W
Ввімкнений екран, 2W

Данні радіоактивного забруднення

Якщо проаналізувати сайт "Український гідрометеорологічний центр" то можна знайти інформацію у JSON форматі котра передається до карт Google у масиві.
Український гідрометеорологічний центр. Рівень радіоактивного забруднення
І якщо зробити вибірку з масиву за номером станції stationCode = 34300 різними засобами, то можна побачити поточні данні радіоактивного забруднення в місті Харків:
{
  "id": "89",
  "stationCode": "34300",
  "stationName": "Харків",
  "latitude": "49.927978",
  "longitude": "36.282428",
  "height": "156",
  "zievert": "61.11",
  "roentgen": "7",
  "measurementDate": "09.4.18",
  "measurementTime": "9",
  "iconURL": "/radiations/edose_pic/50_100.png"
}
А зображення іконки загального стану за адресою "iconURL" з префіксом: https://meteo.gov.ua,  тобто: https://meteo.gov.ua/radiations/edose_pic/50_100.png
Для пошуку даних з масиву в OpenWRT можна використати пакунок jq.
opkg update
opkg install jq

jq  ".[] | select(.stationCode == \"${cityid}\") "
Скрипт для передачі вибраних даних до брокера:
#!/bin/sh
url=https://meteo.gov.ua/radiations/dataSourceFiles/googleMapDataSource.json
cityid=34300
topic=wrt/rad-inet
user=userwrt
passw=password81
wget -q --no-check-certificate -O - "${url}" | \
 jq  ".[] | select(.stationCode == \"${cityid}\") " | \
 mosquitto_pub -h 127.0.0.1 -u ${user} -P ${passw} -t ${topic} -s -q 1 -r

В результаті тестування іконка загального стану не звантажувалася до програми, тому я замінив її різним кольором тексту в залежності від рівня зараження.
on Display для виділення кольором рівня радіації

Деякі візуальні доопрацювання

Використовуючи можливості обробки у JavaScript, використовуючи JSON parser цієї мови, я об'єднав параметри для більш ефективного відображення.
Так опис погоди об'єднав з іконкою погоди замінивши заголовок при обробці скриптами on Receive та on Display:
Обробка даних про іконку погоди та опису


Для температури об'єднав декілька параметрів:
Отримання та обробка даних температури
Показ даних температури
Додав статичне зображення мапи з урахування даних хмарності, завантажуючи її за адресою: https://meteo.ua/var/zip/Sputn-24.jpg, оновлюючи її кожні 3600 секунд.
Інфрачервоний канал - Хмарність
 В результаті маю такий вигляд додатку:
MQTT Dash та погода.

Максимум / Мінімум

Якщо треба відслідковувати максимальні мінімальні значення, наприклад температури, то зберігаємо їх данні в об'єктах скрипт обробки. О 4:10 щодня, макс. мін. значення фіксуюються поточними значеннями.
on receive:
if (!event.data) { event.data = {}; } 
obj=JSON.parse(event.payload).main;
event.data['fl'] = obj.feels_like;
event.data['temp'] =obj.temp;
d=new Date();
m=d.getMinutes();
h=d.getHours();
r=(h==4 && m==10);
if (r || !event.data['tmax']) 
 event.data['tmax'] = obj.temp;
if (r || !event.data['tmin']) 
 event.data['tmin'] = obj.temp;
if (event.data['temp']>event.data['tmax'] )
  event.data['tmax']=event.data['temp'];
if (event.data['temp']<event.data['tmin'] )
  event.data['tmin']=event.data['temp'];
on display:
if (event.data) {
t=event.data['tmax']
+"\n[ "+event.data['temp']
+" ]\n"+event.data['tmin'];
event.text=t;
}
Для скидання значень "max/min" до поточних.
on tap:
if (!event.data) { event.data = {}; } 
if (event.data['temp']){
event.data['tmax'] = event.data['temp'];
event.data['tmin'] = event.data['temp'];
}

Дата / час

Для показу поточного часу можна використати JavaSctipt клас Date.
on display:
d=new Date();
event.text=d.toTimeString().split(' ')[0];
Температура зі значеннями Максим./[ Поточ. ]/Мінім. , поточний час

 Усі налаштування

Для використання Export / Import налаштувань через топік: metrics/exchange можна використати мої усі налаштування з очищеними значеннями для lastPayload за допомогою редактору jsoneditoronline.org:
[
  {
    "decimalPrecision": 0,
    "displayPayloadValue": true,
    "maxValue": 100,
    "minValue": 0,
    "postfix": "%",
    "prefix": "",
    "progressColor": -64,
    "enableIntermediateState": true,
    "enablePub": false,
    "enteredIntermediateStateAt": 0,
    "intermediateStateTimeout": 0,
    "jsOnReceive": "",
    "jsonPath": "main.humidity",
    "lastJsonPathValue": "57",
    "lastPayload": "",
    "qos": 1,
    "retained": false,
    "topic": "wrt/temp-inet",
    "topicPub": "",
    "updateLastPayloadOnPub": true,
    "id": "c28934a3-37bc-4e2f-9565-7ad1d70d0b04",
    "jsBlinkExpression": "",
    "jsOnDisplay": "",
    "jsOnTap": "",
    "lastActivity": 1587754200,
    "longId": 4,
    "name": " Вологість",
    "type": 3
  },
  {
    "mainTextSize": "SMALL",
    "postfix": "",
    "prefix": "",
    "textColor": -49088,
    "enableIntermediateState": true,
    "enablePub": false,
    "enteredIntermediateStateAt": 0,
    "intermediateStateTimeout": 0,
    "jsOnReceive": "if (!event.data) { event.data = {}; } \n \nobj=JSON.parse(event.payload).main;\nevent.data['fl'] = obj.feels_like;\nevent.data['temp'] =obj.temp;\nd=new Date();\nm=d.getMinutes();\nh=d.getHours();\nr=(h==4 && m==10);\n\nif (r || !event.data['tmax']) \n event.data['tmax'] = obj.temp;\nif (r || !event.data['tmin']) \n event.data['tmin'] = obj.temp;\nif (event.data['temp']>event.data['tmax'] )\n  event.data['tmax']=event.data['temp'];\nif (event.data['temp']= 11",
    "jsOnDisplay": "val=99",
    "jsOnTap": "",
    "lastActivity": 1587754200,
    "longId": 7,
    "name": "Вітер, м/с",
    "type": 1
  },
  {
    "imageUrl": "http://openweathermap.org/img/wn/04n@2x.png",
    "kind": 2,
    "openUrl": "",
    "reloadInterval": 300,
    "enableIntermediateState": true,
    "enablePub": false,
    "enteredIntermediateStateAt": 0,
    "intermediateStateTimeout": 0,
    "jsOnReceive": "if (!event.data) { event.data = {}; } \nobj=JSON.parse(event.payload).weather[0];\nif (!obj.icon.startsWith('http')){\n event.data['ico'] = 'http://openweathermap.org/img/wn/'+obj.icon+'@2x.png';\n}\nevent.data['tit'] = obj.description;",
    "jsonPath": "weather[0].icon",
    "lastJsonPathValue": "04n",
    "lastPayload": "",
    "qos": 0,
    "retained": false,
    "topic": "wrt/temp-inet",
    "topicPub": "",
    "updateLastPayloadOnPub": true,
    "id": "66f4abfc-3c59-4932-be4c-22f7a421516f",
    "jsBlinkExpression": "",
    "jsOnDisplay": "if (event.data['ico']!==event.url){\n event.url=event.data['ico'];\n}\nevent.name=event.data['tit'];",
    "jsOnTap": "",
    "lastActivity": 1587754200,
    "longId": 3,
    "name": "Зобр. погоди",
    "type": 5
  },
  {
    "imageUrl": "https://meteo.ua/var/zip/Sputn-24.jpg?u=62501",
    "kind": 1,
    "openUrl": "",
    "reloadInterval": 3600,
    "enableIntermediateState": true,
    "enablePub": false,
    "enteredIntermediateStateAt": 0,
    "intermediateStateTimeout": 0,
    "jsOnReceive": "",
    "jsonPath": "weather[0].icon",
    "qos": 0,
    "retained": false,
    "topic": "wrt/temp-inet",
    "topicPub": "",
    "updateLastPayloadOnPub": true,
    "id": "120b696a-04f5-45a3-82f1-a87991cfefef",
    "jsBlinkExpression": "",
    "jsOnDisplay": "",
    "jsOnTap": "",
    "lastActivity": 1587754193,
    "longId": 12,
    "name": "",
    "type": 5
  },
  {
    "mainTextSize": "MEDIUM",
    "postfix": "",
    "prefix": "",
    "textColor": -4144960,
    "enableIntermediateState": true,
    "enablePub": false,
    "enteredIntermediateStateAt": 0,
    "intermediateStateTimeout": 0,
    "jsOnReceive": "if (!event.data) { event.data = {}; } \n \nobj=JSON.parse(event.payload);\nevent.data['r'] = obj.roentgen;\nevent.data['z'] =obj.zievert;",
    "jsonPath": "roentgen",
    "lastJsonPathValue": "10",
    "lastPayload": "",
    "qos": 1,
    "retained": false,
    "topic": "wrt/rad-inet",
    "topicPub": "",
    "updateLastPayloadOnPub": true,
    "id": "e512c0d1-0cf2-47d2-be05-18d835091824",
    "jsBlinkExpression": "val>11",
    "jsOnDisplay": "event.text=event.data['z']+\"\\n\"\n+event.data['r'];\n\nc='#ffffff';\nv=event.data['z'];\nif (v > 0 && v < 50 ){\n c='#00D0FC';\n} else if (v >= 50 && v < 100){\n c='#B4FF33';\n} else if (v >= 100 && v < 150){\n c='#BFFF33';\n} else if (v >= 150 && v < 200){\n c='#FF6000';\n} else if (v >= 200 && v < 250){\n c='#FF5E33';\n} else if (v >= 250){\n c='#FF0000';\n}\n\n\n\nevent.textColor = c;",
    "jsOnTap": "",
    "lastActivity": 1587754193,
    "longId": 16,
    "name": "Радіація,                 нЗв/год, мРн/год",
    "type": 1
  },
  {
    "mainTextSize": "MEDIUM",
    "postfix": "",
    "prefix": "",
    "textColor": -1,
    "enableIntermediateState": true,
    "enablePub": false,
    "enteredIntermediateStateAt": 0,
    "intermediateStateTimeout": 0,
    "jsOnReceive": "",
    "jsonPath": "",
    "qos": 0,
    "retained": false,
    "topic": "",
    "topicPub": "",
    "updateLastPayloadOnPub": true,
    "id": "81bffc3d-ab44-4772-ae8a-ee99279ec439",
    "jsBlinkExpression": "",
    "jsOnDisplay": "d=new Date();\nevent.text=d.toTimeString().split(' ')[0];",
    "jsOnTap": "",
    "lastActivity": 0,
    "longId": 15,
    "name": "Поточний час",
    "type": 1
  }
]
Використати можна як зберігти ці налаштуваня до файлу, напиклад export.json, і опублікувати до топіку : metrics/exchange.
mosquitto_pub -h <host>  -u <user>  -P <password>  -t  metrics/exchange -r  -f ./export.json

Наступні плани

Додати за датчиком руху, повідомлення то брокера, коли є рух у приміщенні і за цим повідомленням вмикати екран планшету на певний час.

Немає коментарів:

Коли забув ти рідну мову, біднієш духом ти щодня...
When you forgot your native language you would become a poor at spirit every day ...

Д.Білоус / D.Bilous
Рабів до раю не пускають. Будь вільним!

ipv6 ready