Интерактивный экран-помощник

Изначально, я хотел написать обучающую статью по экрану Nextion, но потом понял, что этого добра навалом в Интернете и желание само собой улетучилось. Вдохновленный одним известным в наших кругах человеком, я решил собрать себе устройство с мониторингом событий на своем youtube канале. Для этого я решил собрать комплект из миникомпьютера Omega 2 и экрана Nextion. В дальнейшем, я смогу изменить задачу этой конструкции, а сейчас душа требует свершений. Я наслишь вдохновился, что и ролик сделал, внизу к статье его прикрепил. Приступаем к работе.

Давайте сразу приведу схематичный вид устройства.

Все просто. К самой омеге подпаиваем USB разъем для флешки и выход со второго UART’а. Блок питания 5 В, для получения 3.3 В ставим конвертор напряжения. Я использовал LM317, но это не лучший вариант, лучше — LM2596 или MP2307. К экрану Nextion подпаиваем 5 В.

Далее я буду делать с омегой всякое. Если вы лишь начинаете разбираться с микрокомпьютерами, то советую посмотреть мои предыдущие видео по омеге:

Подключаем к Omega 2 датчик температуры и влажности HIH6130 и работаем с облаком Azure: https://youtu.be/kabWcZriVCE
Честный обзор миникомпьютера OMEGA 2: https://youtu.be/7MDKTXhi5lI

Теперь надо подключить к Omega 2 ненужную флешку, чтобы увеличить на 5 см объем памяти для установки различных пакетов. Как это сделать очень просто рассказано по следующим ссылкам:

1. Монтирование флешки

2. Создание swap файла для распаковки некоторых пакетов

Swap файл увеличит объем оперативной памяти omega для распаковки некоторых пакетов при установке (например http модуль python’а не установиться без swap файла — проверено).

Теперь будем устанавливать все приложения! В данном моменте я уже считаю, что вы включили омегу, подключились к ней по SSH (пароль — onioneer) и к вашей wifi точке доступа с помощью команды wifisetup. Для начала надо обновить информацию о пакетах:

opkg update

Далее устанавливаем MC (файловый менеджер), NANO (текстовый редактор), PYTHON и PIP:

opkg install mc nano python python-pip

Далее поставим пакет для общения через UART в питоне:

opkg install python-serial

и кучу всяких библиотек которые нам понадобятся:

pip install google-auth google-auth-oauthlib google-auth-httplib2 requests httplib2

Теперь открываем Nextion Editor. И тут опять же есть, что посмотреть по экранам Nextion, чтобы набраться знаний:

Что могут дисплеи Nextion? Обзор возможностей, примеры использования: https://youtu.be/gNqT4LRnNC4
Графический замок на дисплее Nextion HMI: https://youtu.be/p-PbTNAlY8c

Нам необходимо реализовать 2 странички. На первой будет выводиться информация по вашему youtube каналу, на второй будет выводиться время, дата и погода в вашем любимом городе. Я решил выводить следующую информацию: общее количество подписчиков, прирост подписчиков за час, последние пять комментариев на канале. Для этого надо поставить два числовых поля и 5 текстовых. При нажатии на любую часть экрана интерфейс должен переходить на следующую страничку сразу реализуем это, добавив в события Press Event всех элементов на странице следующий код:

page time

На страничке со временем ставим два числовых поля, в которых будут выводиться часы и минуты и текстовые поля двоеточия, температуры, даты и состояния погоды. И так же добавляем код перехода на другую страницу по нажатию.

Теперь пора писать супер код на питоне, скачиваете архив из описания статьи и открываете там два файла minutes.py и hours.py. Начнем со скрипта, который будет запускаться каждую минуту.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import httplib2
import os
import sys
import serial
import time
from datetime import datetime
import requests

from apiclient.discovery import build
from apiclient.errors import HttpError
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import argparser, run_flow

# The CLIENT_SECRETS_FILE variable specifies the name of a file that contains
# the OAuth 2.0 information for this application, including its client_id and
# client_secret.
CLIENT_SECRETS_FILE = «client_secrets.json»

# This OAuth 2.0 access scope allows for full read/write access to the
# authenticated user’s account and requires requests to use an SSL connection.
YOUTUBE_READ_WRITE_SSL_SCOPE = «https://www.googleapis.com/auth/youtube.force-ssl»
API_SERVICE_NAME = «youtube»
API_VERSION = «v3»

# This variable defines a message to display if the CLIENT_SECRETS_FILE is
# missing.
MISSING_CLIENT_SECRETS_MESSAGE = «WARNING: Please configure OAuth 2.0»

# Authorize the request and store authorization credentials.
def get_authenticated_service(args):
flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_READ_WRITE_SSL_SCOPE,
message=MISSING_CLIENT_SECRETS_MESSAGE)

storage = Storage(«youtube-api-snippets-oauth2.json»)
credentials = storage.get()

if credentials is None or credentials.invalid:
credentials = run_flow(flow, storage, args)

# Trusted testers can download this discovery document from the developers page
# and it should be in the same directory with the code.
return build(API_SERVICE_NAME, API_VERSION,
http=credentials.authorize(httplib2.Http()))

args = argparser.parse_args()
service = get_authenticated_service(args)

def print_results(results):
print(results)

# Build a resource based on a list of properties given as key-value pairs.
# Leave properties with empty values out of the inserted resource.
def build_resource(properties):
resource = {}
for p in properties:
# Given a key like «snippet.title», split into «snippet» and «title», where
# «snippet» will be an object and «title» will be a property in that object.
prop_array = p.split(‘.’)
ref = resource
for pa in range(0, len(prop_array)):
is_array = False
key = prop_array[pa]
# Convert a name like «snippet.tags[]» to snippet.tags, but handle
# the value as an array.
if key[-2:] == ‘[]’:
key = key[0:len(key)-2:]
is_array = True
if pa == (len(prop_array) — 1):
# Leave properties without values out of inserted resource.
if properties[p]:
if is_array:
ref[key] = properties[p].split(‘,’)
else:
ref[key] = properties[p]
elif key not in ref:
# For example, the property is «snippet.title», but the resource does
# not yet have a «snippet» object. Create the snippet object here.
# Setting «ref = ref[key]» means that in the next time through the
# «for pa in range …» loop, we will be setting a property in the
# resource’s «snippet» object.
ref[key] = {}
ref = ref[key]
else:
# For example, the property is «snippet.description», and the resource
# already has a «snippet» object.
ref = ref[key]
return resource

# Remove keyword arguments that are not set
def remove_empty_kwargs(**kwargs):
good_kwargs = {}
if kwargs is not None:
for key, value in kwargs.iteritems():
if value:
good_kwargs[key] = value
return good_kwargs

print(«Start…/n»)

#open UART
ser = serial.Serial(‘/dev/ttyS1’,9600)
print(ser.name)

#get weather
def weather():
s_city = «Irkutsk,RU»
city_id = 2023469
appid = «ваш id с сайта погоды»
try:
res = requests.get(«https://api.openweathermap.org/data/2.5/weather»,
params={‘id’: city_id, ‘units’: ‘metric’, ‘lang’: ‘ru’, ‘APPID’: appid})
data = res.json()
# print «conditions: «, data[‘weather’][0][‘description’]
ser.write(‘condition.txt=»‘+data[‘weather’][0][‘description’].encode(‘koi8-r’)+'»‘)
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)
# print datetime.fromtimestamp(int(data[‘dt’]))
# print «Temp:», data[‘main’][‘temp’]
ser.write(‘grad.txt=»‘+str(data[‘main’][‘temp’])+'»‘)
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)
# print «davlenie», data[‘main’][‘pressure’]
# print «Vlajnost», data[‘main’][‘humidity’]
except Exception as e:
print(«Exception (weather):», e)
pass

#get trend of subcribes
def send_fucking_number(number):
file=open(«save_subs.txt»)
save_subs_str=file.read()
file.close()
old_number=int(save_subs_str)
print old_number
ser.write(‘dsub.val=’+str(number-old_number))
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)

#get count of subs
def channels_list_by_id(service, **kwargs):
kwargs = remove_empty_kwargs(**kwargs) # See full sample for function
results = service.channels().list(**kwargs).execute()
subs_count=int(results[‘items’][0][‘statistics’][‘subscriberCount’])
ser.write(‘sub_count.val=’)
ser.write(str(subs_count))
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)
send_fucking_number(subs_count)

#get of list of comments
def comment_threads_list_all_threads_by_channel_id(service, **kwargs):
kwargs = remove_empty_kwargs(**kwargs) # See full sample for function
results = service.commentThreads().list(
**kwargs
).execute()

#print_results(results[‘items’][0][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’])
ser.write(‘t0.txt=»‘+results[‘items’][0][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’][0:127].encode(‘koi8-r’).replace(‘»‘, ‘$’)+'»‘)
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)
# print_results(results[‘items’][1][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’])
ser.write(‘t1.txt=»‘+results[‘items’][1][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’][0:127].encode(‘koi8-r’).replace(‘»‘, ‘$’)+'»‘)
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)
# print_results(results[‘items’][2][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’])
ser.write(‘t2.txt=»‘+results[‘items’][2][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’][0:127].encode(‘koi8-r’).replace(‘»‘, ‘$’)+'»‘)
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)
# print_results(results[‘items’][3][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’])
ser.write(‘t3.txt=»‘+results[‘items’][3][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’][0:127].encode(‘koi8-r’).replace(‘»‘, ‘$’)+'»‘)
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)
# print_results(results[‘items’][4][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’])
ser.write(‘t4.txt=»‘+results[‘items’][4][‘snippet’][‘topLevelComment’][‘snippet’][‘textOriginal’][0:127].encode(‘koi8-r’).replace(‘»‘, ‘$’)+'»‘)
ser.write(‘xFF’)
ser.write(‘xFF’)
ser.write(‘xFF’)

subs_count=0

channels_list_by_id(service,
part=’snippet,contentDetails,statistics’,
id=’ваш id’)
comment_threads_list_all_threads_by_channel_id(service,
part=’snippet,replies’,
allThreadsRelatedToChannelId=’ваш id’)
weather()

Первая часть скрипта была нагло скопирована с примеров работы с youtube API на github’е: https://github.com/youtube/api-samples/tree/master/python

Там можно подсмотреть и работу с другими функциями сайта.

Функция ser.write() отправляет в uart данные. Чтобы вывести в числовое поле экрана значение мы должны отправить команду следующего вида:

n0.val=X0xFF0xFF0xFF

где n0 имя числового поля, X значение, которое мы хотим отправить.

Для текстового поля:

t0.txt=»строка»0xFF0xFF0xFF

где t0 имя текстового поля.

Любая команда завершается тремя байтами FF, поэтому при отправке комментариев, я заменяю в них двойные кавычки на знак доллара, чтобы не было ошибок.

Собственно, чтобы у вас заработали эти скрипты вы должны получить youtube key: https://code.google.com/apis/youtube/dashboard/

и пройти регистрацию на сайте погоды и получить там key: https://home.openweathermap.org/users/sign_up

Так же, в архиве я прикрепил скрипт search_city.py. Откройте его и вставьте ваш key с сайта погода и впишите свой город, чтобы узнать его идентификатор. Этот идентификатор, потом нужно скопировать в функцию weather в переменную city_id.

Чтобы скрипты запускались автоматически я решил использовать cron. Для этого я сделал два простеньких bash скрипта вида

cd /root

python minutes.py

Переходим в папку со скриптами и запускаем скрипт. После этого открываем таблицу задач:

nano /etc/crontabs/root

И создаем две задачи:

1-59/1 * * * * * /root/boot_min.sh

0 * * * * * /root/boot_h.sh

Первая команда запускает каждую минуту с первой по 59 минуты скрипт boot_min.sh, вторая запускает в 0 минуту скрипт boot_h.sh. Главное не забыть сделать эти скрипты запускаемыми chmod +x /root/boot-min.sh

После перезагрузки данные автоматически начнут обновляться.

Купить миникомпьютер Omega 2 в России удобней всего на сайте Амперо: https://ampero.ru/collection/omega-2 Лично я советую присмотреться к вариантам с маркировкой S (Omega 2S и Omega 2S Plus). У них исполнение SMD, а значит их удобней паять и больше полезных выводов.

Как я и говорил, все просто.Основная сложность для начинающего (при наличии соответствующих железок) это умение обращаться с Linux. Но в современных реалиях радиолюбительства это надо исправлять, на что и направлена эта статья.

К статье я прикрепил архив, в котором лежат все исходники и файлики, которые я использовал. Если появятся вопросы или предложения пишите, постараюсь на них ответить.


Прикрепленные файлы:

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

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