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

Изначально, я хотел написать обучающую статью по экрану 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. Монтирование флешки: https://goo.gl/eqoKZM

2. Создание swap файла для распаковки некоторых пакетов: https://goo.gl/C69tVD

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

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

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


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

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

Ваш адрес email не будет опубликован.