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

Изначально, я хотел написать обучающую статью по экрану 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. Однако в современных реалиях радиолюбительства это надо исправлять, на что и направлена эта статья. 

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