
Понравилась статья HabraTab — девайс для хаброзависимых, где описана разработка устройства для визуализации рейтинга пользователя на Хабре.
И мне очень захотелось подобное устройство, вот только было несколько но:
- Очень лень было делать, заказывать и паять печатную плату
- Еще хотелось выводить рейтинг последней статьи, но хардкодить адрес и каждый раз пересобирать прошивку показалось очень муторно.
- Разработка на С/С++ меня не пугает, так как занимаюсь этим более 20 лет, но писать что-то под Arduino у меня душа не лежит. И это не говоря про необходимость настройки системы сборки под конкретный микроконтроллер.
Короче, немного поразмыслив, было принято решение делать свое устройство для визуализации рейтингов на Хабре, и как обычно с блекджеком и… ну вы поняли. И самое главное, чтобы можно было собирать устройство из покупных деталей с Алиэкспресса для максимально простого повторения и кодить на чем-нибудь попроще, чем на С/С++.
Аппаратная часть
Не мудрствуя лукаво, нашел самый дешевый микроконтроллер в связке с экраном:
-
Raspberry Pi Pico W (RP2040) с модулем беспроводной связи ~ чуть более 600 руб.

-
Макетная платы для Pi Raspberry Pi Pico с 3,5-дюймовым сенсорным экраном (с прицелом на будущее развитие) ~ 1600 руб.

А так как Raspberry Pi Pico W поддерживает разработку не только на С/С++, но и имеет интерпретатор Micropython, то на этом я и решил остановиться.
Raspberry Pi Pico W — это плата микроконтроллера на основе микроконтроллера Raspberry Pi RP2040. Он имеет два ядра ARM Cortex-M0+, работающими на частоте до 133 МГц, 256кб RAM, 30 GPIO и большим количеством разнообразных интерфейсов. Для хранения прошивки и данных используется флеш память 2 Мб для кода и данных.
Характеристики:
- USB 1.1 с поддержкой устройства и хоста
- Низкомощный режим сна и спящий режим
- 26 многофункциональных контактов GPIO
- 2 × SPI, 2 × I2C, 2 × UART, 3 × 12-bit ADC, 16 × управляемых ШИМ каналов
- Часы реального времени (RTC)
- Датчик температуры
- Ускоренные вычисления с плавающей точкой на чипе
- 8 программируемых I/O (PIO) портов для пользовательской периферийной поддержки
- Wi-Fi модуль
Входное напряжение питания:
- Через USB: 5 В
- Через пин VSYS: 1,8–5,5 В
- Напряжение логических уровней: 3,3 В
- Потребляемый ток: до 140 мА
- Размеры: 52,7×21×12,3 мм
Описание работы с отладочной платой
Принципиальной схемы HabrScore не привожу, так как никаких изменений в покупные платы не вносилось, а вся сборка устройства заняла 5 секунд.
Программная часть
Чтобы иметь всегда актуальную информацию не только о рейтинге пользователя, но и о его последней публикации, делаю запрос по адресу https://habr.com/ru/users/ <USER> /posts/. На этой странице присутствует и рейтинги пользователя и список его статей, поэтому достаточно одного запроса к Хабру, чтобы получить всю интересующую информацию.
Парсинг страницы сперва делал на базе обычной версии Python и только после его отладки портировал наработки на MicroPython.
# Read about program at https://habr.com/ru/post/723334/ # Source at https://github.com/rsashka/HabrScore USER = 'rsashka' WIFI_SSID = '' WIFI_PASS = '' TIMEZONE_SEC = 10800 QUERY_SEC = 20 import os import sys import network import time import ntptime import ussl import usocket import gc import machine # Query Habr and parse response def habr_query(user, marks): result = dict(marks) for key in marks: result[key] = None try: url = 'https://habr.com/ru/users/'+user+'/posts/' _, _, host, path = url.split('/', 3) # copy & paste from urequests s = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM) ai = usocket.getaddrinfo(host, 443, 0, usocket.SOCK_STREAM) ai = ai[0] s = usocket.socket(ai[0], usocket.SOCK_STREAM, ai[2]) s.connect(ai[-1]) s = ussl.wrap_socket(s, server_hostname=host) s.write(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8')) data = None while True: # Read small chunks because there is not enough RAM for readline() temp = s.read(1024) if (data is not None): data = temp temp = s.read(1024) data += temp else: # First reading data = b'' continue if data: # We are looking for only the necessary keys as substrings for key in marks: # The key is looked up only once if (result[key] is not None): continue start = data.find(marks[key]) if (start == -1): continue # Get data between angle brackets pos = data.find(b">", start) end = data.find(b"<", pos) if (pos>0 and end>0): value = data[pos:end] result[key] = value.decode().strip(' \n\t\r><'); else: # End of data break except: print('\nBegin machine.soft_reset()\n') # Software reset and restart MicroPython machine.soft_reset() return result # Output screet on the EP-0164 (https://aliexpress.ru/item/1005004743550177.html?sku_id=12000030313984981) import machine from micropython import const from ili934xnew import ILI9341, color565 import glcdfont import tt14 import tt24 import tt32 from random import randint SCR_WIDTH = const(320) SCR_HEIGHT = const(240) SCR_ROT = const(3) #fonts = [glcdfont,tt14,tt24,tt32] spi = machine.SPI( 0, baudrate=40000000, miso=machine.Pin(4), mosi=machine.Pin(7), sck=machine.Pin(6)) print(spi) display = ILI9341( spi, cs=machine.Pin(13), dc=machine.Pin(15), rst=machine.Pin(14), w=SCR_WIDTH, h=SCR_HEIGHT, r=SCR_ROT) update_screen = True CLR_BG = color565(255, 255, 255) def message(msg, msg2=None, clr_txt=color565(0, 0, 0), clr_bg=CLR_BG): update_screen = True display.set_color(color565(0, 0, 0), clr_bg) display.erase() display.set_color(clr_txt, clr_bg) display.set_pos(15,110) display.set_font(tt32) display.print(msg) if(msg2): display.set_pos(15,150) display.set_font(tt24) display.print(msg2) def error(msg, msg2=None): message(msg, msg2, color565(255, 0, 0)) def status(msg): display.fill_rectangle(0, 220, SCR_WIDTH, 30, CLR_BG) display.set_color(color565(100, 100, 100), CLR_BG) display.set_pos(5,223) display.set_font(tt14) display.print(msg) res = None print(os.uname()); # Print title sync_done = False last_query = time.time() update_screen = True counter = 0; # Main loop led = machine.Pin(0, machine.Pin.OUT) wlan = network.WLAN(network.STA_IF) while(True): led.value(not led.value()) wlan.active(True) if(not wlan.isconnected()): status('Connect to WiFi ...') #print(wlan.scan()) start_s = time.time() wlan.connect(WIFI_SSID, WIFI_PASS) while not wlan.isconnected(): if (time.time() - start_s) > 5: error('WiFi connection error!', 'Check SSID and password.') time.sleep(1) counter+=1 if(counter > 10): error('Performing a hard reboot!') time.sleep(2) # Hardware reset machine.reset() wlan.active(True) time.sleep_ms(500) wlan.connect(WIFI_SSID, WIFI_PASS) print(wlan.ifconfig()) #print(wlan.status()) if(not sync_done): status('Sync local time ...') try: print("Local time before synchronization:%s" %str(time.localtime())) ntptime.settime() print("Local time after synchronization:%s" %str(time.localtime())) sync_done = True except: error('Error syncing time') print("Error syncing time") time.sleep(1) # Search anchors in html page query = { # For user 'KARMA': b'tm-karma__votes', 'SCORE': b'tm-votes-lever__score-counter tm-votes-lever__score-counter tm-votes-lever__score-counter_rating', # For article 'VIEWS': b'class="tm-icon-counter__value', 'VOTES': b'class="tm-votes-meter__value tm-votes-meter__value', 'BOOKMARK': b'bookmarks-button__counter', 'COMMENTS': b'tm-article-comments-counter-link__value', } HEAD_BGR = color565(10, 0, 0) if(update_screen): display.set_color(color565(0, 0, 0), CLR_BG) display.erase() display.fill_rectangle(0, 0, SCR_WIDTH, 40, HEAD_BGR) display.set_color(color565(255, 255, 255), HEAD_BGR) display.set_pos(10,4) display.set_font(tt32) display.print("HabrScore") update_screen = False if ((res is None) or (time.time() - last_query > QUERY_SEC)): last_query = time.time() status('Request about user "@'+USER+'"') new = habr_query(USER, query) if(new['SCORE'] and new['KARMA']): res = new status('Request completd') else: status('Request fail. Use old data') #res = {'VOTES': '+1', 'BOOKMARK': '14', 'KARMA': '96', 'SCORE': '52.1', 'COMMENTS': '29', 'VIEWS': '4.2K'} #habr_query(USER, query) else: display.set_pos(200,4) display.set_font(tt32) display.set_color(color565(255, 255, 255), HEAD_BGR) if(sync_done): _, _, _, hour, minute, second, _, _ = time.localtime(time.time() + TIMEZONE_SEC) curr_time = "{:02}:{:02}:{:02}".format(hour, minute, second) else: curr_time = "No time" display.print(curr_time) status("Score update after {} seconds".format(last_query + QUERY_SEC - time.time())) time.sleep(1) continue print(res) # time.sleep(5) # Print user rating # USER KARMA SCORE USER_OFFSET = 50 display.set_pos(4, USER_OFFSET+4) display.set_font(tt24) display.set_color(color565(0, 0, 0), CLR_BG) display.print(" User:") display.set_pos(80, USER_OFFSET) display.set_font(tt32) display.set_color(color565(100, 150, 180), CLR_BG) display.print("@"+USER) if(res['KARMA'] and len(res['KARMA'])>1 and res['KARMA'][0] == '-'): CLR_KARMA = color565(255, 0, 0) else: CLR_KARMA = color565(0, 255, 0) display.set_pos(4, USER_OFFSET+45) display.set_font(tt24) display.set_color(color565(0, 0, 0), CLR_BG) display.print("Karma:") display.set_pos(85, USER_OFFSET+40) display.set_font(tt32) display.set_color(CLR_KARMA, CLR_BG) if(res['KARMA']): display.print(res['KARMA']) display.set_pos(150, USER_OFFSET+45) display.set_font(tt24) display.set_color(color565(0, 0, 50), CLR_BG) display.print("Score:") display.set_pos(250, USER_OFFSET+40) display.set_font(tt32) if(res['SCORE']): display.print(res['SCORE']) # Print the rating of the latest article # VOTES BOOKMARK COMMENTS VIEWS ARTICLE_OFFSET = 130 display.set_pos(20, ARTICLE_OFFSET+4) display.set_font(tt24) display.set_color(color565(120, 120, 120), CLR_BG) display.print("Rating of the latest article:") if(res['VOTES'] and len(res['VOTES'])>1 and res['VOTES'][0] == '-'): CLR_VOTES = color565(255, 0, 0) else: CLR_VOTES = color565(0, 255, 0) display.set_pos(30, ARTICLE_OFFSET+40) display.set_font(tt32) display.set_color(CLR_VOTES, CLR_BG) if(res['VOTES']): display.print(res['VOTES']) display.set_pos(90, ARTICLE_OFFSET+40) display.set_font(tt32) display.set_color(color565(0, 0, 0), CLR_BG) if(res['VIEWS']): display.print(res['VIEWS']) display.set_pos(180, ARTICLE_OFFSET+40) display.set_font(tt32) display.set_color(color565(0, 0, 0), CLR_BG) if(res['COMMENTS']): display.print(res['COMMENTS']) display.set_pos(260, ARTICLE_OFFSET+40) display.set_font(tt32) display.set_color(color565(0, 0, 0), CLR_BG) if(res['BOOKMARK']): display.print(res['BOOKMARK']) display.set_font(tt14) display.set_color(color565(180, 180, 180), CLR_BG) display.set_pos(25, ARTICLE_OFFSET+70) display.print('Votes') display.set_pos(100, ARTICLE_OFFSET+70) display.print('Views') display.set_pos(160, ARTICLE_OFFSET+70) display.print('Comments') display.set_pos(240, ARTICLE_OFFSET+70) display.print('Bookmarks') sys.exit()
Для разработки и отладки кода под HabrScore пользовался средой разработки Thony Python IDE
При портирование на MicroPython пришлось немного повозится из-за того, что Raspberry Pi Pico W вообще очень мелкий микроконтроллер с мизерным объемом RAM, которого иногда не хватает для буфера под считанную страницу, а иногда бывают глюки и при установке обычного SSL соединения.
Поэтому пришлось немного переделать функцию чтения GET запросов и сделать потоковый парсинг получаемых данных, а в некоторых местах даже добавить перезагрузку всего устройства.
Финальные исходники прошивки на MicroPython с библиотеками и шрифтами для экрана можно скачать в репозитории на GitHub
Инструкция по запуску HabrScore
Запустить все это хозяйств можно следующим образом:
- Скачиваем загрузчик MicroPython для Raspberry Pi Pico W
- При подключении USB к компьютеру удерживаем нажатой кнопку Boot Select. После этого в доступных USB девайсах должен быть виден флеш накопитель на который и нужно записать скаченный загрузчик. После этого плата сама отключится и перезагрузится уже с новой прошивкой с поддержкой MicroPython.
- Скачиваем репозиторий с кодом.
- В файле main.py вписываем имя пользователя Хабра, название WiFi сети и пароль для неё
- Запускаем Thony Python IDE и подключаем плату
- В параметрах Thony Python IDE выбираем правильный интерпретатор MicroPython
- Сохраняем все *.py файлы на устройство (Сохранить как… RP2040 Device)
- Проверяем работу девайса HabrScore и наслаждаемся результатом
Все должно работать примерно вот так:

ссылка на оригинал статьи https://habr.com/ru/company/timeweb/blog/723334/



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