Как стать автором
Обновить

Комментарии 36

простите за оффтоп, опус чисто посмеяться. Так совпало, что сегодня решил приладить лежащую в столе малинку под принт сервер для USB принтера. Накатил raspbian+cups+samba. Дело нехитрое, почти все конфиги на дефолте. Принтер традиционный HP LaserJet 1020. Вобщем сюрпризов не ожидалось. Однако ж…


1) первая группа компов на win10 — малину видно по шаре, запросило логин-пасс, ввел, подошло, открыл шару принтеров, устанавливаю принтер — после выбора драйвера пишет нет связи с принтером, попробовал еще и еще… опа, сработало. Чудеса! Причем на двух машинах не с первого раза. Ладно, думаю, бывает, главное встало.


2) Едем дальше: windows server 2019 — шара ни по имени ни по ip не открывается, пишет сетевой путь не найден. Хотя в проводнике в нетворке малинка отображается как комп! Ну и ладно, не больно-то и хотелось с сервера печатать, всё равно у меня он типо NAS безлюдный.


3) Пришла очередь win7 — шару видно, заходим в принтеры — логин пасс не спрашивало вообще, но при этом открыло. Пробуем установить принтер — ошибка, нет связи с принтером. И магия третьей попытки уже не срабатывает, и даже с десятой не помогло. Пичалька.


Вобщем мораль сей басни такова… жись — боль. А скрещивание ежа с ужом да еще и на арме — боль вдвойне )


PS ну понятно, что надо подкрутить самбу и возможно настройки шары в виндах, завтра займусь. Но меня больше поразило именно количество разных комбинаций "неработоспособности" для одного и того же конфига в связке с разными виндами.

По моему скромному опыту (я все же не админ) с шарами в Windows всегда геморрой :)
Ух и накрутили… А чем плох способ с отправкой формы и методом PUT? Он работает одинаково на всех устройствах и не требует поддержки JS (У нас ведь IoT, верно? А на фоторамке работать будет?)
Немного кода…
<form action="/api/switch_light" method="put" id="disable">
	<input type=hidden name=light value=0 />
</form>
<form action="/api/switch_light/" method="put" id="enable">
	<input type=hidden name=light value=1 />
</form>
<button type="submit" form="disable" value="Submit">On</button>
<button type="submit" form="enable" value="Submit">Off</button>



P. S.: Практичней и наглядней было бы взять Flask/CherryPy в качестве веб-сервера.
Спасибо, с формой тоже возможный вариант.
Не задумывался о поддержке соответствующего атрибута.

Тогда POST, но уж точно не GET, иначе любой поисковой бот который случайно наткнется на веб-страницу будет включать/выключать свет.
так кто-ж голой жо малинкой в интернеты светит? так и до ботнета недалеко!

В таком простом проекте даже bottle.py сойдёт. А как только перестанет хватать (что вряд-ли) на flask всего несколько минут мигрировать.
И читать проще код будет, и функционала больше

Там автор про асинхронность затеял… Тогда aiohttp уж надо было брать. Уж куда компактнее и прозрачнее получилось бы.

Асинхронность тут только на стороне Javascript. А asyncio для начинающих вообще штука мрачная, лучше и не пробовать :)
А при заходе на страничку такую можно выводить состояние светодиода? Я понимаю, что сервер может хранить где-то флажок, но можно ли считать состояние пина и его уже вернуть?
напоминает картинку с хлебом и автобусом, вряд ли что-то серьезнее hello world'а на джанге будет работать нормально
Будет. Но БД надо размещать на другом сервере, или использовать in-memory. Долгое время на raspberry pi использовал Asterisk (до 4-х конкурентных звонков) и пара WSGI-приложений.

Нельзя размещать приложения требовательные к:
— сети (реализована поверх usb)
— IO (microSD)
— времени (нет хардварных часов в комплекте)

А так ARM — вполне годится в качестве домашней тестовой площадки.

Вместо ваших подавляющих ошибки try-except'ов


try:
    import RPi.GPIO as GPIO
except ModuleNotFoundError:
    pass

можно использовать куда более элегантные конструкции из стандартной библиотеки:


with contextlib.suppress(ModuleNotFoundError):
    import RPi.GPIO as GPIO

def rasperrypi_cleanup():
    with contextlib.suppress(Exception):
        GPIO.cleanup()

Хотя я бы, конечно, сделал класс-адаптер для работы с аппаратной частью и при ошибке инициализации создавал экземпляр мокапа для отладочных целей с соответствующим ворнингом:


class RPiDrvAbstract:
    def __init__(self, led_pin: int = 21):
        self.led_pin = led_pin

    def init(self):
        log.debug('GPIO init')

    def pin_out(self, pin: int, value: bool)
        log.debug(f'GPIO pin #{pin} set to {value}')

    def claenup(self):
        log.debug('GPIO cleanup')

class RPiDrv(RPiDrvAbstract):
    def __init__(self, *av, **kw):
        super().__init__(*av, **kw)
        import RPi.GPIO as GPIO
        self.GPIO = GPIO

    def init(self):
        super().init()
        self.GPIO.setmode(GPIO.BCM)
        self.GPIO.setup(led_pin, GPIO.OUT)

    def rasperrypi_pinout(pin: int, value: bool):
        super().pin_out(pin, value)
        self.GPIO.output(pin, value)

    def cleanup(self):
        super().cleanup()
        self.GPIO.cleanup()

try:
    drv = RPiDrv()
    drv.init()
except Exception as e:
    log.warninig(f'RPi initialization error {e}. Mockup used.')
    drv = RPiDrvAbstract()
    drv.init()
Спасибо, хороший вариант.
Неплохо бы в назваии статьи (в первой части тоже) указывать какой именно сервер вы тут поднимаете, их ведь много хороших и разных
Здесь не рассматривается запуск готового сервера, используется свой собственный на Python.
Я это понял, но какой сервер вы пишете на Python? Веб? БД? Терминалов? Этого из заголовка неясно, вот я о чем
Спасибо за статью.
Со светодиодами понятно, они уже пропитаны Дмитриями Осиповыми.
Напишите про диммер-полоску для светодиода. Для многих будет актуально. Как плавно гасить и включать с веб-страницы?
Дайте ссылку, что за полоска такая.

Если обычная светодиодная лента — смотрите в сторону ШИМ, поддержка есть в Python. Примерное описание есть здесь: medium.com/@danidudas/how-to-connect-rgb-strip-led-lights-to-raspberry-pi-zero-w-and-control-from-node-js-70ddfec19f0b
Не совсем лента, но тоже PWM. Есть рука —
и есть ее проект на github
Управление с web-страницы бегунками. Метод GET, как понимаю (судя по видео и коду).
Хотелось бы услышать ваше экспертное мнение, хорош ли GET в данном случае для управления бегунками или есть более рациональный вариант.
Методически правильнее POST, да. По стандарту GET должен использоваться только для чтения: www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

Но это не значит, что многие так не делают :)
Методы одинаково быстро работают? И не будет ли здесь уместнее java? Проблема в том, что при быстрым взаимодействии (поркутить сразу несколько бегунков с короткими паузами), в том числе при выводе на эту же страницу видео с gstreamer, рука начинает откровенно подвисать и потом либо «нагоняет» упущенное потоком движений либо стопорится.

*управление RGB в статье — артиллерия по воробьям. Сейчас rgb идут с ик-модулями, командам с пульта к которым можно обучить rm-mini например и управлять удаленно без rasberry. **А pigpio daemon (в статье о нем) еще тот фрукт.
Возможно, паузы из-за того, что Flask настроен на работу в однопоточном режиме.

stackoverflow.com/questions/14814201/can-i-serve-multiple-clients-using-just-flask-app-run-as-standalone

Хотя возможно это было сделано специально, если PCA9685_pwm не поддерживает многопоточность.
То есть, PCA, рассчитанная на 16 серв, управляет каждым по очереди? Вот почему она 150 р. стоит!
Все же нет, посмотрел сейчас исходник github.com/adafruit/Adafruit_Python_PCA9685/blob/master/Adafruit_PCA9685/PCA9685.py, каналы назначаютс отдельно.

Думаю, когда юзер двигает сразу несколько слайдеров, генерится большое число запросов одновременно. Возможно, можно настроить многопоточность в Flask, или даже имело бы смысл использовать websocket, стало бы быстрее.
Самый простой вариант для пробы — добавить threaded=true как описано здесь: medium.com/@dkhd/handling-multiple-requests-on-flask-60208eacc154

Тогда желательно добавить блокировку, чтобы несколько запросов к PCA9685_pwm.set_pwm не выполнялись одновременно, иначе в I2C будут неправильные данные, если все смешается.

Сама по себе PCA9685 умеет генерирует 16 ШИМов совершенно независимо друг от друга. Задать новые значения ШИМа одновременно, правда, не получится (и авторы микросхемы не заморачивались с тем, чтобы новые значения применялись одновременно), однако там есть возможность загрузить новые значения за один обмен по шине. С учётом скорости шины в 1 МГц (правда, насколько помню, RPi умеет только до 100 кГц) получается довольно бодро.


Правда, господа из Adafruit этот режим не осилили, и пишут мало того, что в каждый канал по отдельности, так ещё и в каждый канал по 4 транзакции вместо одной...

Благодарю, попробую переварить.

Да что там переваривать-то...


Вам надо добавить установку 5-го бита в регистр MODE1 (см. даташит, страница 14) куда-нибудь в PCA9685 __init__()
и переделать


    def set_pwm(self, channel, on, off):
        """Sets a single PWM channel."""
        self._device.write8(LED0_ON_L+4*channel, on & 0xFF)
        self._device.write8(LED0_ON_H+4*channel, on >> 8)
        self._device.write8(LED0_OFF_L+4*channel, off & 0xFF)
        self._device.write8(LED0_OFF_H+4*channel, off >> 8)

во что-то вроде


    def set_pwm(self, channel, on, off):
        """Sets a single PWM channel."""
        # onL, onH, offL, offH
        self._device.writeList(LED0_ON_L + 4*channel, [on&0xff, on>>8, off&0xff, off>>8])

Это уже ускорит обмен в раза в 2-3. Для дальнейшего ускорения надо переделать архитектуру — собирать все параметры в одну кучу, и засылать единоразово по какому-то событию.
Кстати, я был неправ — в этом случае все значения ШИМов будут обновляться одновременно, по окончанию передачи.


И да, есть большая вероятность, что "затык" в чём-то другом. Но слово "Flask" мне не очень знакомо, здесь я не помогу...

Сам код до безобразия прост —
код
from flask import Flask
from flask import request

import time
#import atexit

# Importiere die Adafruit PCA9685 Bibliothek
import Adafruit_PCA9685
#from pca9685 import *

# Initialise the PCA9685 using the default address (0x40).
PCA9685_pwm = Adafruit_PCA9685.PCA9685()
#servo = PCA9685()

# Alternatively specify a different address and/or bus:
#pwm = Adafruit_PCA9685.PCA9685(address=0x41, busnum=2)

# Set frequency to 100hz, good for l298n h-bridge.
PCA9685_pwm.set_pwm_freq(60)
#PCA9685.setPWM(1,on)

# Configure min and max servo pulse lengths
servo_min = 150 # Min pulse length out of 4096
servo_max = 600 # Max pulse length out of 4096

app = Flask(__name__)

app.route("/")
def web_interface():
html = open(«index.html»)
response = html.read().replace('\n', '')
html.close()
return response

app.route("/set_servo1")
def set_servo1():
speed = request.args.get(«speed»)
print («Received » + str(speed))
PCA9685_pwm.set_pwm(0, 0, int(speed))
return «Received » + str(speed)

# 2 servos (pca — 1,2 channels) contolled by «servo-2» slider on web-page
app.route("/set_servo2")
def set_servo2():
speed = request.args.get(«speed»)
a=tuple(speed)
#print (int(a[2]))
x=0
if int(a[0])==1: #value servo 150-199
if int(a[1])==5:
x=(int(a[2]))*5
elif int(a[1])==6:
x=(int(a[2]))*5+50
elif int(a[1])==7:
x=(int(a[2]))*5+100
elif int(a[1])==8:
x=(int(a[2]))*5+150
elif int(a[1])==9:
x=(int(a[2]))*5+200

elif int(a[0])==2: # value servo 200-299
if int(a[1])==0:
x=(int(a[2]))*5+250
elif int(a[1])==1:
x=(int(a[2]))*5+300
elif int(a[1])==2:
x=(int(a[2]))*5+350
elif int(a[1])==3:
x=(int(a[2]))*5+400
elif int(a[1])==4:
x=(int(a[2]))*5+450
elif int(a[1])==5:
x=(int(a[2]))*5+500
elif int(a[1])==6:
x=(int(a[2]))*5+550
elif int(a[1])==7:
x=(int(a[2]))*5+600
elif int(a[1])==8:
x=(int(a[2]))*5+650
elif int(a[1])==9:
x=(int(a[2]))*5+700

elif int(a[0])==3: # value servo 300-399
if int(a[1])==0:
x=(int(a[2]))*5+750
elif int(a[1])==1:
x=(int(a[2]))*5+800
elif int(a[1])==2:
x=(int(a[2]))*5+850
elif int(a[1])==3:
x=(int(a[2]))*5+900
elif int(a[1])==4:
x=(int(a[2]))*5+950
elif int(a[1])==5:
x=(int(a[2]))*5+1000
elif int(a[1])==6:
x=(int(a[2]))*5+1050
elif int(a[1])==7:
x=(int(a[2]))*5+1100
elif int(a[1])==8:
x=(int(a[2]))*5+1150
elif int(a[1])==9:
x=(int(a[2]))*5+1200

elif int(a[0])==4: # value servo 400-499
if int(a[1])==0:
x=(int(a[2]))*5+1250
elif int(a[1])==1:
x=(int(a[2]))*5+1300
elif int(a[1])==2:
x=(int(a[2]))*5+1350
elif int(a[1])==3:
x=(int(a[2]))*5+1400
elif int(a[1])==4:
x=(int(a[2]))*5+1450
elif int(a[1])==5:
x=(int(a[2]))*5+1500
elif int(a[1])==6:
x=(int(a[2]))*5+1550
elif int(a[1])==7:
x=(int(a[2]))*5+1600
elif int(a[1])==8:
x=(int(a[2]))*5+1650
elif int(a[1])==9:
x=(int(a[2]))*5+1700

elif int(a[0])==5: # value servo 500-599
if int(a[1])==0:
x=(int(a[2]))*5+1750
elif int(a[1])==1:
x=(int(a[2]))*5+1800
elif int(a[1])==2:
x=(int(a[2]))*5+1850
elif int(a[1])==3:
x=(int(a[2]))*5+1900
elif int(a[1])==4:
x=(int(a[2]))*5+1950
elif int(a[1])==5:
x=(int(a[2]))*5+2000
elif int(a[1])==6:
x=(int(a[2]))*5+2050
elif int(a[1])==7:
x=(int(a[2]))*5+2100
elif int(a[1])==8:
x=(int(a[2]))*5+2150
elif int(a[1])==9:
x=(int(a[2]))*5+2200

elif int(a[0])==6: # value servo 500-599
if int(a[1])==0:
x=(int(a[2]))*5+2250

#print(x)
speed2=(int(speed)*4)-x
#speed2=servo_max-x

PCA9685_pwm.set_pwm(1, 0, int(speed))
PCA9685_pwm.set_pwm(2, 0, int(speed2))
return «Received » + str(speed2)

app.route("/set_servo3")
def set_servo3():
speed = request.args.get(«speed»)
print («Received » + str(speed))
PCA9685_pwm.set_pwm(3, 0, int(speed))
return «Received » + str(speed)

app.route("/set_servo4")
def set_servo4():
speed = request.args.get(«speed»)
print («Received » + str(speed))
PCA9685_pwm.set_pwm(4, 0, int(speed))
return «Received » + str(speed)

app.route("/set_servo5")
def set_servo5():
speed = request.args.get(«speed»)
print («Received » + str(speed))
PCA9685_pwm.set_pwm(5, 0, int(speed))
return «Received » + str(speed)

app.route("/set_servo6")
def set_servo6():
speed = request.args.get(«speed»)
print («Received » + str(speed))
PCA9685_pwm.set_pwm(6, 0, int(speed))
return «Received » + str(speed)

app.route("/set_servo7")
def set_servo7():
speed = request.args.get(«speed»)
print («Received » + str(speed))
PCA9685_pwm.set_pwm(7, 0, int(speed))
return «Received » + str(speed)

if __name__ == "__main__":
app.run(host='0.0.0.0', port=8181, debug=True)


Так понимаю, интересна строка:
PCA9685_pwm.set_pwm_freq(60)

Как интегрировать def set_pwm? Скормить все функции?

Дело осложняется еще тем, что после динамичных передвижений слайдеров PCA задирает ток до 1,5 А и при этом проседает напряжение. И все стопорится, подвешивая raspberry.
Может тестовый пост накатать на эту тему?

*Поменял GET на POST, не сильно погоду изменило.

Осознать всю портянку кода не смог, извините. Особенно непонятны почти одинаковые ряды elif'ов. Вроде б оно в пару условий схлопывается...


set_pwm_freq — это установка частоты ШИМа. Единое на все каналы (особенность PCA). Насколько я понимаю в сервах (а понимаю не очень много...), рекомендуемая частота для встроенного в серву драйвера — полсотни-сотня герц. Т.е. тут всё вроде б нормально.


Как интегрировать def set_pwm?

What do you mean? Я просто предлагал переписать адафруктовое безобразие.


Дело осложняется еще тем, что после динамичных передвижений слайдеров PCA задирает ток до 1,5 А и при этом проседает напряжение.

Сама PCA вряд-ли столько может потребить (там абсолютный максимум по паспорту — 0.4 ватта), это какой-то моторчик с ума сходит.
Кажется, Дмитрий прав, и тут действительно идёт запись в несколько потоков.
Попробуйте натыкать косты.. отладку:


    def set_pwm(self, channel, on, off):
        if self.cur_channel is not None:
            как_там_у_фласка_запись_в_лог ("Error! Simultanious write in two channels!!")
        self.cur_channel = channel
        # тут, собственно, пишем
        self.cur_channel = None

И в случае появления подобных сообщений думать, как их лечить (а лечить надо обязательно! какая каша будет на I2C в этом случае, даже разбираться лень).
Хотя, если желание поразбираться есть, купите у китайцев клон Saleae Logic. Замечательная штуковина для отладки всяких подобных околожелезных вопросов. Стоит рублей 400 всего.


PS DmitrySpb79, мы вам тут не сильно мешаем? Питон есть, распбери тоже, но от темы мы явно отклонились :-)

Мне не жалко :)

Насколько я понимаю, Flask по умолчанию работает в однопоточном режиме, и когда юзер двигает сразу несколько слайдеров, на сервер идет параллельно десяток запросов от html-страницы, которые встают в очередь.

Но просто разрешить многопоточность в Flask недостаточно, надо переписать код работы с PCA9685, чтобы библиотека корректно параллельные запросы обрабатывала.
разрешить многопоточность в Flask

… как раз не нужно. Оно ж наверняка гораздо быстрее, чем обмен по I2C. Имхо.


Наверное, где-то что-то портится, в регистры PCA пишется мусор, и какой-то моторчик сходит с ума (я не в курсе, что будет с сервой, если её кормить "нестандартным" ШИМом).


Диагностика — для начала выводом передаваемых данных из адафрукт-библиотеки, потом логическим анализатором на входе и на выходе PCA.
Опять же, имхо околожелезячного программиста.

Осознать всю портянку кода не смог, извините. Особенно непонятны почти одинаковые ряды elif'ов. Вроде б оно в пару условий схлопывается...

elifы — это костыль для серво, чтобы он крутился в обратную сторону параллельно с другим серво. Так как в «плече» два серво.
Отладка во flaske уже включена (в конце кода debug=True).
В том-то и фикус, сто в дебагер ничего не падает. С точки зрения программы все работает корректно. Так же корректно подвисает.
Здесь видео работы с нагрузкой, но и без нее проблемы возникают:
видео



я не в курсе, что будет с сервой, если её кормить «нестандартным» ШИМом

Просто не работает серва.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории