Русский
Русский
English
Статистика
Реклама

Велосипед

Перевод Умная перчатка для велосипедистов

14.07.2020 10:11:05 | Автор: admin


Перевод с сайта instructables.com, автор проекта: Matlek

В данной статье я подробно расскажу процесс изготовления умной перчатки и её светодиодной панели, предназначенных для повышения безопасности велосипедистов и других людей, путешествующих по дорогам. Сначала можно посмотреть небольшую демонстрацию работы:



Как это работает


В перчатке находится плата Arduino, собирающая данные с гироскопа и акселерометра. Код использует модель крохотного машинного обучения tinyML и распознаёт жесты: каждое движение руки анализируется и превращается в сигнал (рука наклоняется влево, вправо, вперёд, назад, и т.п.). Сигнал отправляется по Bluetooth (BLE) на другой микроконтроллер, к которому подсоединена светодиодная матрица (которую, например, можно закрепить на рюкзак). Сообразно полученному сигналу матрица выводит определённые последовательности символов так, чтобы другие водители и велосипедисты могли понимать, что собирается сделать велосипедист (к примеру, это могут быть стрелки влево, вправо, или текст).









Происхождение проекта


Во-первых, я езжу на велосипеде на работу, и провожу в седле более часа в день (проезжая порядка 22 км). Это всегда интересно, однако я живу в одном из самых густонаселённых городов Франции, где часто происходят происшествия с участием автомобилей и велосипедистов. Также Марсель худший во Франции город для велосипедистов там отчаянно не хватает велодорожек. Поэтому данный проект посвящён как увеличению безопасности велосипедистов, так и попыткам обратить внимание городских властей на эту проблему.

Во-вторых, этот проект поможет всем участникам движения пообщаться и лучше понимать друг друга. С моей точки зрения большая часть неприятностей, происходящих на дороге, связана с тем, что некоторые участники движения неправильно поняли других, что в результате привело к испугу, а потом к агрессии. Мне хочется, чтобы такое устройство помогло участникам движения лучше понимать друг друга. Стрелки показывают направление, а ещё можно выводить текст по буквам (однако я целиком и полностью выступаю за вежливые и конструктивные надписи, во избежание конфликтов).

Почему умная перчатка?


Я начал работу над проектом зимой, и холодная погода мотивировала меня на то, чтобы закрепить устройство на перчатке. Но я быстро понял, что идея была не очень хорошей, потому что в нашей местности летом довольно жарко. Поэтому я решил, что лучше всего будет поместить устройство в коробку и закрепить на руке. Но поскольку я не знал, как назвать его по-другому, я решил оставить слово перчатка в названии.

Умная происходит от техники машинного обучения, которую я использовал в данном проекте.

Вдохновение


Проект в основном является смесью двух других проектов. Я не начинал работу с нуля, а пользовался их наработками, которые потом развивал далее. Вот, чем я вдохновлялся при разработке:
распознавание жестов при помощи Arduino Nano 33 BLE SENSE.
не какой-то конкретный проект, а концепция использования светодиодных матриц для велосипедистов. Таких проектов полно некоторые используют рюкзаки с интегрированными панелями, другие просто предлагают готовую матрицу, которую можно поместить куда угодно. В любом случае, эти светодиодные матрицы управляются при помощи пульта дистанционного управления, а не распознавания жестов.

Комплектующие


Для 3D-печати 3D-принтер или доступ к таковому.

Электроника


  • Arduino Nano 33 BLE SENSE;
  • Ещё один МИ с BLE (Arduino Nano 33 BLE, Arduino 33 BLE SENSE, Arduino nano 33 IOT, ESP32, и т.д.). Я решил использовать плату на ESP32.
  • Светодиодная полоска (WS2812B). Я использовал 160 светодиодов, чтобы получить матрицу 208;
  • Четырёхуровневый буфер с 3 В до 5 В: 74AHCT125.
  • Конденсатор на 1000 мкФ.
  • Переключатели SPST, 3 шт.
  • Макетная плата.
  • Провода.
  • Батарейка 9 В.
  • Внешний аккумулятор.


Другое


  • Винты и гайки М3.
  • Застёжка-липучка.


Шаг 1: подготовка (МИ, код)






Прочитав статью про Arduino и машинное обучение, я решил тоже попробовать. Поскольку в последнее время появилось несколько новых плат Arduino Nano, я сделал сравнительную табличку, чтобы сделать наилучший выбор перед покупкой.



Все платы интересные, однако для распознавания жестов я мог использовать только одну Arduino Nano 33 BLE SENSE. Лишь у неё есть нужные датчики и поддержка Tensorflow Lite. Ещё один интересный момент на платах Arduino Nano 33 IOT, BLE и BLE SENSE есть собственный Bluetooth, поэтому любую из них можно использовать на светодиодной матрице для приёма BLE сигналов.

Загруженный в плату код основан на множестве различных программ для Arduino, найденных мною при разработке. Поэтому перед началом работы я решил проверить их с найденными мною примерами.

Поиграемся с BLE


В данном проекте связь по Bluetooth имеет решающее значение, поскольку именно так сигнал отправляется с датчиков на светодиодную матрицу. До этого я никогда не связывал две платы Arduino по BLE. Поэтому я практиковался со следующими примерами из библиотеки ArduinoBLE:
  • Скетч LedControl, используемый с платой Arduino Nano 33 BLE Sense и кнопкой с притягивающим резистором, подсоединённым к контакту 2. Пример опрашивает BLE-периферию, пока не найдёт сервис с UUID 19b10000-e8f2-537e-4f6c-d104768a1214. После его обнаружения и установления соединения он будет удалённо управлять периферийным светодиодом BLE по нажатию кнопки.
  • Скетч для светодиода и Arduino Nano 33 IoT.


К сожалению, со скетчем для светодиода у меня возникло множество проблем 3 платы сломались при его загрузке. Понятия не имею, в чём там была проблема, но я решил заменить плату Arduino на другой МИ с BLE плату ESP32. С новой платой я использовал следующее:

  • Скетч BLE_write из библиотеки BLE ESP32 ARDUINO. Я добавил несколько изменений, чтобы она работала с платой Arduino Nano 33 BLE SENSE. На шаге 10 вы сможете сравнить скетч BLE_write и скетч Smartglove_BLE_LED-matrix, который я написал и загрузил.


Поиграемся со встроенными RGB светодиодами


Вы знали, что у платы Arduino Nano 33 BLE SENSE есть встроенные RGB светодиоды? В данном проекте они пригодятся для проверки правильной работы распознавания жестов. Мы должны проверять, что сигнал был отправлен на светодиодную матрицу однако поскольку панель, скорее всего, находится на спине велосипедиста, ему будет трудно понять, что распознавание жестов сработало и сигнал был отправлен.

Тут не было ничего сложного, я просто немного подправил пример Blink. Из кода видно, что красный светодиод находится на контакте 22, зелёный на контакте 23, синий на контакте 24. Входной сигнал LOW включает светодиод, HIGH выключает.

const int LED_BUILTIN_RED = 22;const int LED_BUILTIN_GREEN = 23;const int LED_BUILTIN_BLUE = 24;// функция setup запускается один раз после включения или перезагрузки платыvoid setup() {  // initialize digital pin LED_BUILTIN as an output.  pinMode(LED_BUILTIN_RED, OUTPUT);  pinMode(LED_BUILTIN_GREEN, OUTPUT);  pinMode(LED_BUILTIN_BLUE, OUTPUT);}// функция loop повторяется вечноvoid loop() {  digitalWrite(LED_BUILTIN_RED, LOW);   // включить LED (HIGH  уровень напряжения)  delay(1000);                       // подождать секунду  digitalWrite(LED_BUILTIN_RED, HIGH);    // выключить LED, понизив напряжение до LOW  delay(1000);                       // подождать секунду digitalWrite(LED_BUILTIN_GREEN, LOW);   // включить LED (HIGH  уровень напряжения)  delay(1000);                       // подождать секунду  digitalWrite(LED_BUILTIN_GREEN, HIGH);    // выключить LED, понизив напряжение до LOW  delay(1000);   // подождать секунду    digitalWrite(LED_BUILTIN_BLUE, LOW);   // включить LED (HIGH  уровень напряжения)  delay(1000);                       // подождать секунду  digitalWrite(LED_BUILTIN_BLUE, HIGH);    // выключить LED, понизив напряжение до LOW  delay(1000);                       // подождать секунду}


Поиграемся с распознаванием жестов и tinyML


Наконец, я изучил руководство по использованию машинного обучения на Arduino, и попрактиковался с примером распознавания жестов. Пример делится на три основные части:
  • Распознавание данных с программой IMU_Capture (и Arduino Nano 33 BLE sense);
  • Обучение модели на записанных данных на google colab (на компьютере);
  • Использование обученной модели на Arduino с IMU_Classifier для распознавания образов (опять на плате Arduino).


Шаг 2: перчатка 1/6 (электроника)












С шагов 2 по 7 я привожу одну и ту же схему, обозначая на ней шаги с тем, чтобы вам проще было понять процесс изготовления перчатки.

Схема электроники для перчатки очень простая:
  • Плата Arduino.
  • Батарейка на 9 В (я использую аккумулятор).
  • Переключатель SPST.


Шаг 3: перчатка 2/6 корпус






















Корпус простой, и состоит всего из двух частей, распечатанных на 3D-принтере:
  • В жёлтой части находится плата Arduino, аккумулятор и переключатель. Отверстия в корпусе позволяют перезаряжать батарею и перепрограммировать плату Arduino без необходимости разбирать корпус.
  • Чёрная часть это крышка, защищающая аккумулятор и плату.


На руку я креплю её полоской липучки.

Также я нарисовал логотип, который потом приклеил на крышку. Он обозначает велосипедиста, на которого смотрят сверху, и в нём есть три стрелочки прямо, налево и направо. Четвёртая стрелочка отстоит от трёх остальных, потому что велосипеды не ездят назад.

Файлы


content.instructables.com/ORIG/FS2/L3M3/K9N93ZYW/FS2L3M3K9N93ZYW.stl

content.instructables.com/ORIG/F72/21NG/K9N93ZZG/F7221NGK9N93ZZG.stl

content.instructables.com/ORIG/FD3/NVS8/K9N93ZZI/FD3NVS8K9N93ZZI.stl

Шаг 4: перчатка 3/6: запись данных
















После сборки устройства приходит время записи данных. Цель записать каждый жест многократно. Я установил порог для гироскопа, и когда он выходит за это значение, Arduino начинает выводить записанные данные на монитор.

Я записал следующие жесты:
  • Рука указывает влево (стандартный жест велосипедистов, обозначающий поворот налево).
  • Торможение (жест пальцами, тянущимися к рычагу тормоза).
  • Рука наклоняется назад.
  • Рука наклоняется вперёд.
  • Рука наклоняется влево.
  • Рука наклоняется вправо.


Естественно, вы можете записывать свои жесты.

Для записи данных я написал программку, переключающую на светодиоде цвета после каждых 20 движений. Это помогало мне понимать, когда нужно переходить к другому жесту. Я подсоединил Arduino к компьютеру, открыл программу для отслеживания порта и положил ноутбук в рюкзак.

Записав все жесты, я перешёл к последнему этапу скопировал данные, выведенные в программу, и сохранил их в формате csv.

content.instructables.com/ORIG/FC7/B0JT/K9UEA78V/FC7B0JTK9UEA78V.ino

Шаг 5: перчатка 4/6: обучение








Для обучения я использовал следующий пример, исправив в нём всего несколько строк. Перед обучением стоит проверить, что для каждого жеста у вас есть свой csv-файл, и данные в каждом файле относятся именно к этому жесту.

На сайте Google Colab по ссылке в разделе Upload data загрузите свои данные.

В разделе Graph Data (optional) добавьте имя одного из файлов.

filename = Arm_left.csv

Затем исправьте эту строчку, чтобы выводить только данные с гироскопа:

#index = range(1, len(df['aX']) + 1)
index = range(1, len(df['gX']) + 1)

Закомментируйте следующие строки данные акселерометра мы не используем:

#plt.plot(index, df['aX'], 'g.', label='x', linestyle='solid', marker=',')
#plt.plot(index, df['aY'], 'b.', label='y', linestyle='solid', marker=',')
#plt.plot(index, df['aZ'], 'r.', label='z', linestyle='solid', marker=',')
#plt.title(Acceleration)
#plt.xlabel(Sample #)
#plt.ylabel(Acceleration (G))
#plt.legend()
#plt.show()

В разделе Parse and prepare the data добавьте все названия файлов:

#GESTURES = [punch, flex,]
GESTURES = [Arm_left, Brake, Hand_back-tilt, Hand_front-tilt, Hand_left-tilt, Hand_right-tilt]

Измените количество образцов на один жест, если меняли их в коде для Arduino:

#SAMPLES_PER_GESTURE = 119
SAMPLES_PER_GESTURE = 64

Осталось только закомментировать ускорение:

# normalize the input data, between 0 to 1:
# acceleration is between: -4 to +4
# gyroscope is between: -2000 to +2000
tensor += [
#(df['aX'][index] + 4) / 8,
#(df['aY'][index] + 4) / 8,
#(df['aZ'][index] + 4) / 8,
(df['gX'][index] + 2000) / 4000,
(df['gY'][index] + 2000) / 4000,
(df['gZ'][index] + 2000) / 4000
]

После прохода всей программы вы сможете скачать обученную модель.

Файлы


content.instructables.com/ORIG/F7A/GLEK/K9UEA8Z5/F7AGLEKK9UEA8Z5.csv

content.instructables.com/ORIG/FV1/853G/K9UEA8Z6/FV1853GK9UEA8Z6.csv

content.instructables.com/ORIG/FQH/OAZD/K9UEA8Z7/FQHOAZDK9UEA8Z7.csv

content.instructables.com/ORIG/F7N/P7AG/K9UEA8Z9/F7NP7AGK9UEA8Z9.csv

content.instructables.com/ORIG/FD4/WZRM/K9UEA8ZA/FD4WZRMK9UEA8ZA.csv

content.instructables.com/ORIG/F6W/7SO2/K9UEA8ZB/F6W7SO2K9UEA8ZB.csv

Шаг 6: перчатка 5/6: код для Arduino



Итоговый мой код для умной перчатки это смесь следующих программ:
  • пример LED из библиотеки ArduinoBLE (Peripheral>LED).
  • IMU_Classifier отсюда.


Подробно тут я распространяться о них не буду, просто рекомендую прочитать оригинальные программы, чтобы лучше понимать происходящее.

Добавьте свою модель в код, и её можно будет испытывать!

Файлы


content.instructables.com/ORIG/F9N/4SBK/K9UEA98M/F9N4SBKK9UEA98M.h

content.instructables.com/ORIG/FKZ/ODO9/KB52VXZK/FKZODO9KB52VXZK.ino

Шаг 7: перчатка 6/6: испытания










Как видно из видеоролика, светодиод загорается по-разному в зависимости от распознанного жеста:



Шаг 8: светодиодная матрица 1/4: электроника












Как я уже упоминал, при закачке скетча из библиотеки ArduinoBLE для светодиода на Arduino Nano 33 BLE SENSE я столкнулся с некоторыми проблемами. Поэтому я решил вместо этой платы использовать ESP32. Поэтому на приведённых фотографиях вы можете увидеть обе платы.

Поскольку обе платы, Arduino Nano 33 BLE SENSE и ESP32, работают с логикой на 3,3 В, я добавил четырёхуровневый буфер с 3 В до 5 В (74AHCT125), как рекомендуется в инструкции от Adafruit.

Также я добавил конденсатор на 100 мкФ для защиты светодиода от резких перепадов напряжения.

Всю схему я собрал на макетной плате.

Видно, что я задействовал оба разъёма внешнего источника питания, поскольку испугался, что светодиодной матрице потребуется слишком большой ток. Поэтому матрица и МИ питаются от разных разъёмов внешнего источника питания.

Шаг 9: светодиодная матрица 2/4: корпус




























Мне нужен был сборный корпус для светодиодной матрицы. Поэтому он состоит из нескольких частей (а ещё потому, что мой 3D-принтер очень крохотный), и я предусмотрел в них отверстия для болтов.

Для подсоединения панели я снова воспользовался липучкой.

Файлы


content.instructables.com/ORIG/FH6/TB4H/K9N93ZZJ/FH6TB4HK9N93ZZJ.stl

content.instructables.com/ORIG/FK3/BZPC/K9N93ZZK/FK3BZPCK9N93ZZK.stl

content.instructables.com/ORIG/FMU/ZRTY/K9N93ZZL/FMUZRTYK9N93ZZL.stl

content.instructables.com/ORIG/F38/BF1P/K9N93ZZM/F38BF1PK9N93ZZM.stl

content.instructables.com/ORIG/FJC/DQMY/K9N93ZZN/FJCDQMYK9N93ZZN.stl

content.instructables.com/ORIG/F43/ELQV/K9N93ZZQ/F43ELQVK9N93ZZQ.stl

content.instructables.com/ORIG/FJE/C5FG/K9N93ZZR/FJEC5FGK9N93ZZR.stl

content.instructables.com/ORIG/F55/1X43/K9N93ZZS/F551X43K9N93ZZS.stl

Шаг 10: светодиодная матрица 3/4: код для Arduino














Итоговый код смесь следующих кодов (и их модификация):
  • Пример BLE_Write из библиотеки BLE ESP32 ARDUINO.
  • Пример MatrixGFXDemo64 из библиотеки FastLED NeoMatrix.


Подробно тут я распространяться о них не буду, просто рекомендую прочитать оригинальные программы, чтобы лучше понимать происходящее.

content.instructables.com/ORIG/FIR/RETZ/KB52VXP4/FIRRETZKB52VXP4.ino

Шаг 11: светодиодная матрица 4/4: испытания






Настало время всё проверить! После распознавания каждого жеста на светодиодную матрицу отправляется сигнал, и она показывает определённый узор. Видно, что на перчатке светодиоды зажигаются соответственно распознанному жесту.



Шаг 12: итоговые испытания и заключение






Вот как это выглядит вживую:



Я очень доволен получившимся устройством. Благодаря проекту я гораздо увереннее чувствую себя с tinyML и BLE. С тех пор я купил ещё Arduino Nano 33 IOT, и сейчас занимаюсь весьма интересным проектом, о котором напишу позже. Что бы я изменил во второй версии описанного мною устройства:
  • Крышка для перчатки. Сейчас она держится на корпусе только за счёт того, что туго надевается. Однако как-то во время поездки я задел что-то рукой, крышка соскочила и разбилась. В следующей версии прикручу её винтами.
  • Корпус для светодиодной матрицы. Я почти сразу понял, что в моём корпусе отсутствует быстрый доступ к USB МИ. А мне хотелось бы иметь доступ, чтобы отлаживать код или менять его. Также без раскручивания корпуса нельзя зарядить внешний источник питания.
  • Больше данных для обучения. Иногда некоторые из жестов не распознаются, а иногда распознаются ошибочно. Думаю, не хватает данных (всего 20 движений для каждого жеста). Больше движений лучше модель, меньше ошибок.


На завершение проекта и написание этого текста у меня ушло несколько месяцев. Если что-то неясно, или какого-то файла не хватает, пишите в комментариях к оригинальной статье.
Подробнее..

Локализация своих скриптов на BASH

05.02.2021 14:16:53 | Автор: admin

Создание меню на BASH задача сама по себе не сложная: "case тебе в руки и echo в спину". Решая её в очередной раз, мне захотелось добавить возможность отображать текст на других языках. Осталось решить, как сделать сам процесс локализации меню более удобным. Если оно большое, то решение "в лоб" превратит его в громоздкую копипасту. Здесь я хотел бы поделиться тем, как решил эту проблему для себя. Надеюсь для кого то это будет не безынтересным.



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


Примечание:

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


Мне не хотелось бы разбивать скрипт на несколько частей и хранить локализацию в отдельных файлах. Скрипт тем и удобен, что его проще использовать одним файлом. Поэтому тексты будем хранить в массивах.


Реализация будет состоять из:


  • буквенного кода языка
  • массива слов
  • преобразователя косвенных ссылок для обращения к массиву
  • обращения к элементам массива
  • создания меню

Теперь рассмотрим подробнее


Зададим язык, добавив короткий буквенный код языка (ru, en), для начала английский:


langset=en

Добавим массивы слов и предложений (по количеству языков):


language_en=( "English" "Quit" "Hi, Habr!" )language_ru=( "Русский" "Выход" "Привет, Хабр!" )

Создадим косвенную ссылку и сформируем из неё новый массив в переменной lng:


lng="language_$langset[@]"; lng=("${!lng}")

То есть здесь для переменной lng создаётся значение, состоящее из части имени массива со словами и кода заданного языка из переменной langset. Далее создаётся массив, который при langset=en будет равен language_en, а при langset=ru будет равен language_ru.
Если языков будет много, то такой подход позволит избавиться от многочисленных if-elif или case. Чтобы изменить язык, достаточно будет добавить массив с переводом и установить язык в переменной langset.


Запустим всё это в консоли:


language_en=( "English" "Quit" "Hi, Habr!" )language_ru=( "Русский" "Выход" "Привет, Хабр!" )langset=enlng="language_$langset[@]"; lng=("${!lng}")echo "${lng[2]}"# Вывод: Hi, Habr!langset=rulng="language_$langset[@]"; lng=("${!lng}")echo "${lng[2]}"# Вывод: Привет, Хабр!

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


Построение меню


А теперь попробуем создать скрипт.
Для примера я взял меню управления командами Git через CLI (command line interface). Это немного надуманно, но хорошо подходит для примера, так как в Git много команд и как раз можно построить единое по своей задаче меню с множеством параметров.


Для начала задаем язык и массив с пунктами меню как это уже делали выше. Ради удобства, я разделил массив на две части: первая это непосредственно меню, вторая информационные сообщения и приглашения командной строки, которые необходимо будет вывести по ходу работы.


#!/bin/bash# Код языкаlangset=en# Пункты менюlanguage_en=( "English" "Quit" "Main menu" "Git: add ALL files/commit" "Git init" "Change language" "Language selection" )message_en=( "English" "Select item" "Wrong! This item does not exist" "Added all files" "Enter you commit" "Changes recorded" "Select a language" "The language has been changed to" "Start the program again" "Menu for language change" )language_ru=( "Русский" "Выход" "Основное меню" "Git: добавить ВСЕ файлы/коммит" "" "" "Выбор языка" )message_ru=( "Русский" "Выберите пункт" "Неверно! Этого пункта не существует" "Добавление всех файлов" "Введите ваш коммит" "Изменения зарегистрированы" "Выберите язык" "Язык изменен на" "Запустите программу заново" "Меню для смены языка" )language_de=( "Deutsch" )message_de=( "Deutsch" "" "" "" "" "" "" "" "Starten Sie das Programm neu" )

Пройдемся по массивам и создадим новые для lng и msg, которые и будут использоваться:


languages() {    lng="language_$langset[@]"; lng=("${!lng}")    msg="message_$langset[@]"; msg=("${!msg}")    for b in ${!language_en[@]} ${!message_en[@]} ; do        if [[ ! ${lng[$b]} ]] ; then            lng[$b]=${language_en[$b]}        fi        if [[ ! ${msg[$b]} ]] ; then            msg[$b]=${message_en[$b]}        fi    done}languages

Чтобы при необходимости иметь возможность применять языковые настройки (это пригодится в следующей статье), заключим всё в функцию и тут же вызовем её.


Небольшое отступление: я уверен, почти всем встречались наполовину переведённые приложения. Особенно этим грешат кастомные прошивки для телефонов. Мне никогда это не нравилось, но в данном случае я всё же решил предусмотреть такую возможность неполного перевода. Это позволит при добавлении нового языка не дублировать все элементы массива, а добавить только те, которые пользователю хотелось бы перевести.


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


Теперь, когда с языками разобрались, приступим к построению меню. Тут всё стандартно: бесконечный цикл, внутри него цикл для вывода элементов на экран и выбор вариантов через case.


Формируем массив для вывода на экран. Только те пункты, которые нам нужны:


local menu0=([1]="${lng[3]}" "${lng[4]}" "${lng[5]}" "${lng[1]}")

Здесь делаем индексацию с 1, чтобы выводить индекс как соответствующий пункт меню.


Добавляем шапку, пробегаемся по индексам через ${!menu0[@]} и выводим на экран:


echoecho "---${lng[2]}---"for op in "${!menu0[@]}" ; do     echo "$op ) ${menu0[$op]}"doneecho ---------- 

Далее предложим пользователю выбрать необходимый пункт. Будем ожидать нажатие цифровой клавиши через read -s -n1 -p. Где -s не отображать введённые данные (чаще используется для ввода паролей). Мы же потом сами отобразим введеный текст в более удобном формате. -p строка приглашения для вывода подсказки. -n1 параметр, считывающий число симмволов ввода. Здесь мы заранее знаем, что пунктов меню будет не более 9 (то есть числа из одной цифры), поэтому при нажатии одного любого символа программа продолжит работу дальше. Не надо будет нажимать Enter для ввода. Немного непривычно, поэтому можно убрать.


# read со строкой приглашенияread -s -n1 -p "${msg[1]}: " item#вывод нажатой клавишиecho "[$item]->: ${menu0[$item]}"

Ну и заключительный оператор выбора case с обработкой введенного значения:


case $item in        # Команды Git, без обработки ошибок (репозитория ведь может и не быть)    1 )     git add .        read -p "${msg[4]}: " comm        git commit -m "$comm"        echo "${msg[5]}" ;;    2 ) git init ;;    3 ) echo "${msg[9]}" ;;        # Выход    4 ) exit ;;        # Обработка остальных клавиш и вывод сообщения об ошибке    * ) echo "[$item]->: ${msg[2]}"; sleep 2 ;;esac        

Здесь case сделан упрощенным, только для примера. В следующей статье я буду использовать его в более универсальном варианте.


Весь код
#!/bin/bash# Код языкаlangset=ru# Меню и сообщенияlanguage_en=( "English" "Quit" "Main menu" "Git: add ALL files/commit" "Git init" "Change language" "Language selection" )message_en=( "English" "Select item" "Wrong! This item does not exist" "Added all files" "Enter you commit" "Changes recorded" "Select a language" "The language has been changed to" "Start the program again" "There will be a menu for changing the language" )language_ru=( "Русский" "Выход" "Основное меню" "Git: добавить ВСЕ файлы/коммит" "" "" "Выбор языка" )message_ru=( "Русский" "Выберите пункт" "Неверно! Этого пункта не существует" "Добавление всех файлов" "Введите ваш коммит" "Изменения зарегистрированы" "Выберите язык" "Язык изменен на" "Запустите программу заново" "Здесь будет меню для смены языка" )language_de=( "Deutsch" )message_de=( "Deutsch" "" "" "" "" "" "" "" "Starten Sie das Programm neu" )languages() {    # Косвенные ссылки и создание нового массива    lng="language_$langset[@]"; lng=("${!lng}")    msg="message_$langset[@]"; msg=("${!msg}")    # Сравнение массивов для проверки на пропущенные элементы    for b in ${!language_en[@]} ${!message_en[@]} ; do        if [[ ! ${lng[$b]} ]] ; then            lng[$b]=${language_en[$b]}        fi        if [[ ! ${msg[$b]} ]] ; then            msg[$b]=${message_en[$b]}        fi    done}languagesmain() {    # Создание и вывод меню на экран    local menu0=([1]="${lng[3]}" "${lng[4]}" "${lng[5]} [$langset]" "${lng[1]}")    while true ; do         echo        echo "---${lng[2]}---"        for op in "${!menu0[@]}" ; do             echo "$op ) ${menu0[$op]}"        done        echo ----------        # Ожидание ввода значения        read -s -n1 -p "${msg[1]}: " item        echo "[$item]->: ${menu0[$item]}"        # Оператор выбора        case $item in                # Команды Git, без обработки ошибок (репозитория ведь может и не быть)            1 ) #               git add .                read -p "${msg[4]}: " comm#               git commit -m "$comm"                echo "${msg[5]}" ;;            2 ) #               git init ;;            3 ) echo "${msg[9]}" ;;                # Выход            4 ) exit ;;                # Обработка остальных клавиш и вывод сообщения об ошибке            * ) echo "[$item]->: ${msg[2]}"; sleep 2 ;;        esac                done}mainexit 0

Это первая статья из двух. В следующей рассмотрим создание многоуровнего меню, разукрасим вывод (как же без этого), а также попробуем сохранить настройки, то есть задать код языка прямо из меню, не открывая Shell-скрипт в текстовом редакторе.


Спасибо, что дочитали до конца. Надеюсь, было не скучно :)

Подробнее..

Локализация своих скриптов на BASH, часть 2

13.02.2021 16:10:43 | Автор: admin

Это продолжение статьи Локализация своих скриптов на BASH. В ней мы используя массивы и косвенные ссылки, научились добавлять в свои скрипты дополнительные языки и переключаться между ними.
В этой статье составим список встроенных языков и зададим выбор языка через меню, построив для этого многоуровневое меню. Чтобы статья не превратилась в один большой кусок кода с описанием каждой строчки, сам код с подробными комментариями я выложу ниже, а здесь затрону только несколько основных моментов.

Создание меню

В прошлой статье для вывода меню на экран и выбора необходимого пункта мы использовали цикл for и оператор выбора case. Очевидно, что для создания многоуровнего меню эти шаги нужно будет повторить несколько раз, то есть для каждого подменю. При этом придется заново создавать шапку меню, внешний вид и так далее.
Хороший способ этого избежать - вынести for, case и read в отдельную функцию (назовём её prints), а далее будем просто передавать в неё необходимые параметры. Все сценарии, которые будут выполнятся при выборе тех или иных пунктов меню также будут вынесены в соответствующие функции.

Так, чтобы добавить в скрипт новое действие:

  • добавляем в языковой массив слова и фразы

  • в массив с основным или дополнительным меню вставляем соответствующий пункт и команду вызова функции

  • добавляем функцию с необходимым фрагментом кода

Для начала создадим основное меню, которое сразу появится на экране при запуске скрипта:

menu0=("${lng[3]};main" "${lng[4]};gitadd" "${lng[5]};gitinit" "${lng[2]};options" "${lng[1]};exit")

Здесь каждый элемент массива задается двумя параметрами: словесное название необходимого пункта меню, а через разделитель ;- имя функции, которая будет вызвана. Сделал это для удобства, чтобы не создавать лишний массив для списка команд. Там, куда это все будет передаваться, элемент разделится на две части.

Поместим массив в функцию main:

main() {    # Массив с основным меню    local menu0=("${lng[3]};main" "${lng[4]};gitadd" "${lng[5]};gitinit" "${lng[2]};options" "${lng[1]};exit")    while true ; do        # Передаем массив в функцию вывода на экран        # Вторым аргументом идет сообщение, которое отобразится в строке приглашения        prints "menu0[@]" "${msg[1]}"    done}main

При таком подходе создание дополнительных меню немного упрощается, например options:

options() {    local menu1=("${lng[2]};options" "${lng[7]} [$langset];langmenu" "${lng[1]};exit")    prints "menu1[@]" "${msg[1]}"

Теперь пришло время рассмотреть функцию prints, которая выводит все эти меню на экран. Сначала поместим в неё конструкцию, разделяющую элемент на две части. Для разделения задействуем команду вырезания данных cut:

if [[ "$1" == "text" ]] ; then    # Для текста меню до разделителя    echo "$2" | cut -d ";" -f 1    returnelif [[ "$1" == "command" ]] ; then    # Для команды после разделителя    echo "$2" | cut -d ";" -f 2    returnfi

Для получения, например, текстового поля вызывать её будем командой ${prints "text" "${menu[0]}"} , где второй аргумент - сам элемент массива.

Небольшое, но важное отступление: В скрипт я добавил возможность раскрашивать вывод на экран в разные цвета. Отвечающий за это код я поместил в функцию colors. Для раскрашивания используются ANSI escape последовательности (вывод echo -e) с расширенной палитрой на 256 цветов.

Также, с прицелом на будущее, я добавил функцию pwds, для отображения даты и полного пути текущей директории. Это на случай, если захочется добавить переход по папкам и выбор файлов.

Я не буду описывать эти функции, но они будут в конечном коде с подробными комментариями.

Продолжим рассматривать функцию вывода меню на экран prints. Задаем массив из массива, переданного в функцию через аргумент:

local menu=("${!1}")

Далее - непосредственно вывод на экран. Выводим дату и текущий путь, затем название меню желтым цветом, название берется из текстовой части 1 элемента массива:

pwdscolors "title" "---$(prints "text" "${menu[0]}")---"

Перебираем в цикле for массив, выводим пункты белым цветом, ожидаем ввод значения read и обрабатываем нажатие через оператор выбора case:

for (( op=1; op < "${#menu[@]}"; op++ )); docolors "item" "$op ) $(prints "text" "${menu[$op]}")"doneecho ----------read -s -n1 -p "$(colors "item" "$2: ")" itemcase $item in[1-$((${#menu[@]}-1))] ) # Вывод выбранного пункта меню зеленым цветомcolors "ok" "[$item]->: $(prints "text" "${menu[$item]}")"# Вызов функции с фрагментом кода$(prints "command" "${menu[$item]}") ;;# Немедленное завершение по [q]"q" ) echo; exit;;# Обработка остальных клавиш и вывод сообщения об ошибке красным цветом* ) colors "err" "[$item]->: ${msg[2]}"; sleep 2 ;;esac
Так будет выглядеть это менюТак будет выглядеть это меню

Построение меню выбора языка

Часть статьи под этим и следующим заголовоком я считаю самой основной. Она получится короткой, но без рассмотренного выше сложно было бы описать последующий функционал.

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

Заранее хочу отметить, что мы не знаем количество и какие языки присутствуют в скрипте. Мы просто ищем их все. Для этого пробегаемся по текущему скрипту, через sed и регулярное выражение находим все имена массивов language_ и добавляем коды языков в массив. То есть из language_ru выкусываем ru:

# [-r] - расширенный синтаксис регулярных выражений# [-n] - вывод только того, что совпадает с шаблономlocal lng_sfx=($(sed -r -n "s/^\s?+language_(\w+)=.*/\1/p" "${0}"))

Учитывая, что мы не знали какие языки у нас будут, мы не могли сразу задать массив для передачи его в prints. Нужно будет собрать его по частям. Сделаем это там же в langmenu:

langmenu(){    local lng_sfx=($(sed -r -n "s/^\s?+language_(\w+)=.*/\1/p" "${0}"))    local menu2=("${lng[7]};langmenu")    for a in ${lng_sfx[@]} ; do        local d="language_$a[@]"; d=("${!d}")        menu2+=("$d;languages set $a")    done    menu2+=("${lng[1]};exit")    prints "menu2[@]" "${msg[6]}"}

Здесь мы:

  1. Создаем массив для вывода меню

  2. В цикле перебираем массив с кодами языков. На каждой итерации создаем косвенную ссылку, чтобы обратиться к 0 элементу соответствующего языкового массива (с записанным нзванием языка). На следующем шаге мы формируем элемент меню: в каждый элемент в текстовую часть добавляем название языка, а через разделитель ;, в командную часть, добавляем команду вызова функции languages и в качестве аргумента ставим код языка. Для английского языка получится "English;languages set en", где set en - аргументы для функции languages.

  3. После цикла добавляем в массив с меню команду выхода

  4. Передаем сформированный массив с языковым меню в функцию вывода на экран prints. Вторым аргументом идет сообщение, которое отобразится в строке приглашения

Меню выбора языкаМеню выбора языка

Сохранение языка через настройки

После выбора языка через меню запомним его через перезапись в скрипте. применим для этого потоковый редактор sed с ключами -i и -r, где -i - редактирование (перезапись) файла, -r - поддержка расширенного синтаксиса регулярных выражений.
Здесь всё просто: ищем первое вхождение строки, начинающейся с langset= и в ней переменную langset= с кодом языка через редактирование перезаписываем новым значением и сразу выходим:

sed -i -r "0,/^\s?+langset=/s/langset=[\"\']?\w*[\"\']?/langset=\"$langset\"/" "${0}"exit

В предыдущей части мы сформировали языковое меню и передали его в функцию prints. Которая, в свою очередь, через оператор выбора case вызывает функцию languages, описанную в прошлой статье и передаёт в неё аргумент set и код выбранного языка. Самое время добавить в languages функцию перезаписи настроек, а также вывести на экран сообщение о смене языка в ТЕКУЩЕЙ локализации. После этого применим новый язык и опять выведем то же самое сообщение на НОВОМ языке:

Функция languages
if [ "$1" == "set" ] ; then# Устанавливаем новый язык из входного аргументаlangset="$2"local df="language_$langset"echo# Сообщение на ТЕКУЩЕМ языке что язык изменен, цвет зеленыйcolors "ok" "${msg[7]} ${!df}. ${msg[8]}"# Применяем настройки языкаlanguages# Сообщение на НОВОМ языке что язык изменен, цвет зеленыйcolors "ok" "${msg[7]} ${lng[0]}. ${msg[8]}"echo# Перезаписываем переменную langset= с кодом языка и выходимsed -i -r "0,/^\s?+langset=/s/langset=[\"\']?\w*[\"\']?/langset=\"$langset\"/" "${0}"exit fi
Установка языкаУстановка языка

Заключение

В этой и прошлой статьях мы создали шаблон, который можно использовать для написания своих скриптов с возможностью отображения экранного меню и сообщений на разных языках. При этом скрипт состоит из одного файла и для добавления нового языка достаточно закинуть массив со словами в начало скрипта, а для добавления какого либо действия - создать функцию и прописать её в меню.

Спасибо, что дочитали до конца.

Весь код с комментариями прикреплен ниже.

Весь код
#!/bin/bash# Код языкаlangset="ru"# Меню и сообщенияlanguage_en=( "English" "Quit" "Options" "Main menu" "Git: add ALL files/commit" "Git init" "Change language" "Language selection" )message_en=( "English" "Select item" "Wrong! This item does not exist" "Added all files" "Enter you commit" "Changes recorded" "Select a language" "The language has been changed to" "Start the program again" "Repository not found\nPlease, select Git init pepository" )language_ru=( "Русский" "Выход" "Настройки" "Основное меню" "Git: добавить ВСЕ файлы/коммит" "" "" "Выбор языка" )message_ru=( "Русский" "Выберите пункт" "Неверно! Этого пункта не существует" "Добавление всех файлов" "Введите ваш коммит" "Изменения зарегистрированы" "Выберите язык" "Язык изменен на" "Запустите программу заново" "Репозиторий не найден\nПожалуйста, инициализируйте репозиторий, выбрав Git init" )language_de=( "Deutsch" )message_de=( "Deutsch" "" "" "" "" "" "" "" "Starten Sie das Programm neu" )language_cn=( "" "" "" "")message_cn=( "" "" "" "" "" "" "" "" "" )# Settings sectionlanguages() {# Функция с языковыми настройками и установкой нового языка# Косвенные ссылки и создание нового массиваlng="language_$langset[@]"; lng=("${!lng}")msg="message_$langset[@]"; msg=("${!msg}")# Сравнение массивов для проверки на пропущенные элементыfor b in ${!language_en[@]} ${!message_en[@]} ; doif [[ ! ${lng[$b]} ]] ; thenlng[$b]=${language_en[$b]}fiif [[ ! ${msg[$b]} ]] ; thenmsg[$b]=${message_en[$b]}fidone# Установка нового языкаif [ "$1" == "set" ] ; then# Устанавливаем новый язык из входного аргументаlangset="$2"local df="language_$langset"# Выводим сообщение на ТЕУЩЕМ языке что язык изменен,# пишем какой выбрали, предлагаем перезапустить программуechocolors "ok" "${msg[7]} ${!df}. ${msg[8]}"# Применяем настройки языкаlanguages# Выводим сообщение на НОВОМ языке что язык изменен# пишем какой выбрали, предлагаем перезапустить программуcolors "ok" "${msg[7]} ${lng[0]}. ${msg[8]}"echo# Через регулярное выражение путем изменения файла# перезаписываем переменную langset= с кодом языка и выходим# [-r] - расширенный синтаксис регулярных выражений# [-i] - редактирование файла# [0,] - только первое вхождениеsed -i -r "0,/^\s?+langset=/s/langset=[\"\']?\w*[\"\']?/langset=\"$langset\"/" "${0}"exit fi}# Применяем настройки языкаlanguagescolors() {# Установка цвета текста и фона. Строки даны полностью,# чтобы можно было просто изменить цифры, ничего не дописывая# Здесь [48] - код расширенной палитры фона, [38] - текста# [5] - 8-битный формат цвета (0-255), [1] - жирный,# [22] - отменить жирный, [0] - сбросить все измененияcase "$1" in# Текст: темно-зеленый (часы)"tm" ) echo -e "\e[48;5;256;38;5;34;22m$2\e[0m" ;;# Фон: светло-синий, текст: белый жирный (часть полного пути)"pt" ) echo -e "\e[48;5;24;38;5;15;1m$2\e[0m" ;;# Текст: светло-желтый жирный (текущая папка)"cf" ) echo -e "\e[48;5;256;38;5;226;1m$2\e[0m" ;;# Текст: темно-зеленый жирный (цвет успешной операции)"ok" ) echo -e "\e[48;5;256;38;5;34;1m$2\e[0m" ;;# Текст: красный жирный (цвет ошибки)"err" ) echo -e "\e[48;5;256;38;5;160;1m$2\e[0m" ;;# Текст: светло-желтый (шапка меню)"title" ) echo -e "\e[48;5;256;38;5;226;22m$2\e[0m" ;;# Текст: белый (пункты меню и строка приглашения)"item" ) echo -e "\e[48;5;256;38;5;15;22m$2\e[0m" ;;esac}pwds() {# Цветное отображение полного пути текущей директории и датыecho echo ----------echo "$(colors 'tm' "[$(date +"%T")]") $(colors 'pt' "${PWD%/*}"/)$(colors 'cf'  "$(basename   "$PWD")")"echo ----------}prints() {# Функция вывода меню на экран# Разделение элемента массива на текст и команду, в качестве разделителя [;]if [[ "$1" == "text" ]] ; thenecho "$2" | cut -d ";" -f 1returnelif [[ "$1" == "command" ]] ; thenecho "$2" | cut -d ";" -f 2returnfi# Задаем массив из массива, переданного в функцию через аргументlocal menu=("${!1}")# Вывод даты и текущего путиpwds# Вывод названия меню желтым цветом, название берется# из текстовой части 1 элемента массива colors "title" "---$(prints "text" "${menu[0]}")---"# Вывод меню на экранfor (( op=1; op < "${#menu[@]}"; op++ )); do# Вывод пунктов меню белым цветом, названия берутся# из текстовой части соответствующего элемента массиваcolors "item" "$op ) $(prints "text" "${menu[$op]}")"doneecho ----------# Ожидание ввода значения, приглашение выводится белым цветомread -s -n1 -p "$(colors "item" "$2: ")" item# Оператор выбораcase $item in# Все числа от 1 до размера всего массива минус 1 (так как индексация массива с 0)# Вывод выбранного пункта меню зеленым цветом название берется# из текстовой части соответствующего элемента массива[1-$((${#menu[@]}-1))] ) colors "ok" "[$item]->: $(prints "text" "${menu[$item]}")"# Вызов функции с фрагментом кода, имя функции берется# из командной части соответствующего элемента массива$(prints "command" "${menu[$item]}") ;;# Немедленное завершение по [q]"q" ) echo; exit;;# Обработка остальных клавиш и вывод сообщения об ошибке красным цветом* ) colors "err" "[$item]->: ${msg[2]}"; sleep 2 ;;esac}# Application sectiongitinit() {# Для примера: фрагмент кода для [git init]git init}gitadd() {# Для примера: фрагмент кода для [git add] - добавить все файлыgit add .# Обработка ошибок. Если статус завершения команды не равен [0]# вывести сообщение об ошибке красным цветом и вернуться в менюif [[ "$?" != "0" ]] ; thencolors "err" "${msg[9]}" sleep 1return 1 fiecho "${msg[3]} ..."# Приглашение и ввод коммитаread -p "$(colors "item" "${msg[4]}: ")" commgit commit -m "$comm"# сообщение о завершении операции зеленим цветомcolors "ok" "${msg[5]}"}# Menu sectionlangmenu() {# Функция создания языкового меню# Проходим по текущему скрипту, через регулярное выражение находим# все имена массивов [language_*] и добавляем коды языков в массив# [-r] - расширенный синтаксис регулярных выражений# [-n] - вывод только того, что совпадает с шаблономlocal lng_sfx=($(sed -r -n "s/^\s?+language_(\w+)=.*/\1/p" "${0}"))# Создаем массив для вывода менюlocal menu2=("${lng[7]};langmenu")# Перебираем в цикле массив с кодами языков, на каждой итерации создаем косвенную ссылку,# чтобы обратиться к 0 элементу соответствующего языкового массива (с записанным нзванием языка)for a in ${lng_sfx[@]} ; dolocal d="language_$a[@]"; d=("${!d}")# Продолжаем формирование массива для вывода языкового меню# В каждый элемент в текстовую часть добавляем название языка, а через# разделитель [;], в командную часть, добавляем команду вызова функции# [languages] и в качестве аргумента ставим код языка. Для английского языка# получится ["English;languages set en"], где [set en] - аргументы для функции [languages]menu2+=("$d;languages set $a")done# Добавляем в меню команду выходаmenu2+=("${lng[1]};exit")# Передаем сформированный массив с языковым меню в функцию вывода на экран# Вторым аргументом идет сообщение, которое отобразится в строке приглашенияprints "menu2[@]" "${msg[6]}"}options() {# Функция создания меню с настройками# В каждый элемент в текстовую часть добавляем название необходимого пункта меню,# а через разделитель [;], в командную часть, добавляем соответствующую команду вызова функцииlocal menu1=("${lng[2]};options" "${lng[7]} [$langset];langmenu" "${lng[1]};exit")# Передаем массив в функцию вывода на экран# Вторым аргументом идет сообщение, которое отобразится в строке приглашенияprints "menu1[@]" "${msg[1]}"}main() {# Функция создания основного меню# В каждый элемент в текстовую часть добавляем название необходимого пункта меню,# а через разделитель [;], в командную часть, добавляем соответствующую команду вызова функции# Здесь и в других массивах, содержащих меню, первая запись - это название и команда вызова текущей функции# Необходимо для того, чтобы передавать название меню для печати шапки и для вызова этой функции при необходимостиlocal menu0=("${lng[3]};main" "${lng[4]};gitadd" "${lng[5]};gitinit" "${lng[2]};options" "${lng[1]};exit")while true ; do# Передаем массив в функцию вывода на экран# Вторым аргументом идет сообщение, которое отобразится в строке приглашенияprints "menu0[@]" "${msg[1]}"done}mainexit 0
Подробнее..

Велосипед, зима и Санкт-Петербург настало время сказочных историй

06.01.2021 00:04:36 | Автор: admin

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

Каким я был, но не остался

Несколько лет назад я был "моноколесником", и тут вдруг судьба забросила мой дом за 8 км от работы. А это, по моим ощущениям, ни то ни се. Вроде и не очень далеко, но ведь и так работа сидячая, а тут еще час в день надо "отстоять" на колесе. Был вариант взять колесо помощнее и начать ездить быстрее, но на этапе выбора мотоциклетного шлема (мотокуртка у меня уже была) супруга насторожилась и мягко, но настойчиво предложила попробовать велосипед.

Я попробовал - и с тех пор являюсь последовательным сторонником велосипедизации мегаполисов. Я даже думаю, что одной из подлинных причин краха СССР было недостаточное внимание партии и правительства к велосипедам. Казалось бы - машин еще не так много, трафик невелик, сделай велодорожки и охраняемые стоянки у школ, заводов и фабрик - и все! Трудящиеся будут радостно крутить педали, попутно сбрасывая стресс, нарабатывая иммунитет и сохраняя окружающую среду. Общество могло быть здоровым и счастливым! Но нет, все ездили общественным транспортом, и это было ужасно.

Моё первое велосипедное лето быстро закончилось и я стал думать, что делать зимой. Поискал материал в Сети, и то, что я нашел, меня сильно впечатлило.

Вот еще ролик. Просмотрев сие, сделал вывод - это и я так могу. Действительно - смог, хотя и обнаружил довольно толстые нюансы. Далее кратко излагаю свой опыт, полученный в условиях "национальных особенностей". Буду рад, если какие-то частицы этого опыта можно применить и в других российских городах.

Убить скользкого дракона

Под "драконом" имеется ввиду опасение, что ездить на велосипеде зимой - скользко и опасно. Это, пожалуй, самый популярный вопрос, из тех, что мне задают (по заданной теме). Приведенные выше ролики демонстрируют, что ездить можно, но, конечно, не так, как летом - зимний процесс не терпит суеты.

Если сравнивать с пешим ходом, то на велосипеде зимой, пожалуй, получше. Несколько раз доводилось помогать упавшим пешеходам дойти до каких-то мест, однажды женщина упала прямо на моих глазах и довольно неудачно - назад. Велосипедисту же зимой невозможно поскользнуться и упасть назад или вперед, так как вертикальная прямая, проложенная через центр тяжести, надежно находится внутри точек опоры:

В целом, считаю падение осторожного велосипедиста событием менее вероятным, чем падение осторожного пешехода1. К тому же, если скорость невелика, всегда есть шанс выставить третью точку опоры - ногу (европейские партнеры делают это таким образом, в пределе это выглядит как-то так).

Первую спичку - на велостоянку

Про волшебные спички

Тема волшебных спичек связана с детскими воспоминаниями о замечательной книге "Шёл по городу волшебник" (фильм по книге, кстати, не нравился):

Знающие люди посмотрев видео сразу укажут, что колесящие зимой на великах европейцы - это другое. И будут совершенно правы, существенных отличий можно указать довольно много. Например - "там" есть места, где железному коню совершенно вольготно живется. Как-то был в командировке, принимающая сторона "водила" по Амстердаму. Там есть, на что посмотреть, помимо улицы с фонарями известного цвета:

Велосипеды в АмстердамеВелосипеды в Амстердаме

Понятно, что так далеко не везде и у "них", но у "нас" такого нет почти нигде.

Чтобы ездить на велосипеде зимой в реалиях российского мегаполиса, одну волшебную спичку надо потратить на появление мест для парковки велосипеда у дома и работы - каждый раз затаскивать заснеженную и грязную металлическую конструкцию в помещение совершенно не хочется. Мне повезло - парковка у моего дома и так есть, да еще и в закрытом дворе, так что к волшебству прибегать не пришлось. И даже у офиса парковка есть! Зимой от нее оставляют небольшую секцию - см. КДПВ.

Вообще, ситуация с паркингом велосипедов постепенно улучшается - велопарковки появляются у магазинов и других общественных мест, а всяческие "шеринги" самокатов и велосипедов вырабатывают у граждан привычку к виду пристегнутых то тут, то там средств передвижения.

Жаба

Надо понимать, что велосипед в Санкт-Петербурге зимой - это "расходник", и он будет деградировать, что называется, на глазах. С этим фактом надо просто смириться, ведь велосипед все время находится на улице, подвергается осадкам, засаливанию, перепадам температуры, выпадению росы и коррозии металла. Дважды в год я отдаю железного коня на ТО производителю, благо, и тут повезло, мастерская - рядом. Практически каждый раз меняют цепь, в этот раз дополнительно был "съеден" передний тормозной узел в сборе, на все ушло 2700 рублей. Вполне терпимо, по сравнению с расходами на семейный Mitsubishi Outlander.

После первого зимнего сезона сильно пострадал узел складывания, его всячески умасляли и что-то там еще делали, но ничего не помогало. В тех редких случаях, когда это складывание требовалось, приходилось использовать силу ноги и заклинаний. Надо сказать, что в третий сезон мастера таки нашли какое-то зелье и теперь мой Shulz складывается практически свободно.

Эх, дороги!

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

Практически Артур Конан Дойл, Собака Баскервилей

Вторую волшебную спичку надо потратить на появление велодорожки от дома к работе. Впрочем, предварительно лучше посмотреть на карту существующих дорожек, в данном случае "экономия на спичках" вполне оправдана:

Информация взята с сайта СПб ГКУ Городской центр управления парковками Санкт-Петербурга. Как видим, кое-где волшебные дорожки и так есть. В процессе написания этой статьи я решил посмотреть, а что открылось в 2020 году и, надо же - Велодорожка на Бухарестской улице соединила три станции метро, что покрывает приличный кусок моего маршрута. В общем-то, там и до этого можно было проехать по тротуару, но одно дело, когда ты едешь среди пешеходов, чувствуя себя чужим, а совсем другое, когда это все происходит легально, а пешеходы, завидев тебя, уступают дорогу.

Если же нет ни волшебных спичек, ни велодорожек, то все равно стоит проанализировать дорожный граф и попытаться найти такой маршрут межу вершинами "Дом" и "Работа", ребрами которого являются либо дороги, по которым почти никто не ездит, либо тротуары, по которым почти никто не ходит. Я езжу практически из центра Санкт-Петербурга (район около м. Площадь Восстания) на юг, и такой маршрут, длиной 8 километров, у меня есть. А теперь еще часть его можно и по велодорожке проложить4.

К слову, длину маршрута 8-10 километров я считаю пределом для комфортной поездки в офис. Если путь длиннее - приезжаешь либо в "мыле", либо очень долго ехать. А так - можно неторопливо доехать до офиса за вменяемое время, по пути обдумывая будущий рабочий день, а "топить", сбрасывая накопившийся стресс, уже в обратную сторону.

Нулевая изотерма января

Российский писатель и историк Максим Кантор отмечает: Есть остроумное определение Фернана Броделя (французский историк, 1902-1988 г.г.), где проходит граница между Европой и не-Европой: по нулевой изотерме января. Потому что западнее теплее, и там один режим работы, крестьянский в первую очередь, круглогодичный. А восточнее холодно там на печи приходится лежать всю зиму и лапти плести [2]. Конечно, это остроумие западного человека, но суть нулевая изотерма января в этом выражении поймана точно: с одной стороны западная цивилизация, с другой российская. Здесь можно даже расширить трактовку: с одной стороны российская, а с другой все остальные мировые цивилизации.


Нулевая изотерма января

Это весьма интересное разделение Европы и не-Европы, которое опирается на солидный базис метеорологической "бигдаты", а не на субъективное желание "быть в Европе". Однако интерпретация этой "изотермы", как водится, нетривиальна. Данных-то много - как группировать, что суммировать, что на что делить, как округлять? Все это влияет на результат.

Например, есть такой вариант (отсюда: Average temperature Europe (January)):

"Желтый" Амстердам тут по метеорологическому критерию попадает в Европу, а вот "зеленый" Копенгаген - нет. А ведь там тоже многие ездят зимой на великах:

Нет, все-таки Копенгаген - точно Европа! Как же быть с метеорологическим критерием? Все становится на свои места, если "нулевую изотерму" считать по средней максимальной температуре дня.

Город

Ср. макс. дневная температура января, C

Европа?

Амстердам

+6

Да

Копенгаген

+3

Да

Париж

+7

Да

Цюрих

+3

Да

Киев

-3

Нет

Минск

-4

Нет!

Санкт-Петербург

-5

Нет!!

Москва

-6

Нет!!!

Кажется, в этом определенно что-то есть... Имеют место и исключения, типа Осло, но исключения, как известно, подтверждают правило.

Применительно к поездкам на велосипеде могу сказать, что между +3 и -5 разница весьма заметна, особенно если надо неторопясь преодолевать расстояния около 8 км. Несколько лайфхаков хозяйке на заметку:

  • Очки хорошо спасают от встречного холодного потока

  • Свободный капюшон тоже отлично помогает, но, увы, минус обзор2

  • Лыжные брюки поверх джинс дают двойной эффект - защита от холода и от грязи. Быстро снимаются и надеваются

  • Тяжелые зимние ботинки - классная обувь для велосипедиста при температуре около нуля и ниже3

  • Вещи лучше возить в багажнике - так и удобнее, и меньше вероятность "употеть"

  • Полагаю, что ездить в зимой Санкт-Петербурге в среднем опаснее, чем по ту сторону нулевой изотермы; у "них" скорее всего колеса почти всегда "добивают" до асфальта - на улице-то "плюс"

  • Шины на зиму лучше все-таки менять на что-то более "злое"

Заключение

  • Санкт-Петербург - не Европа (метеорологически, по критерию "нулевая изотерма января")

  • Там, где температурные условия схожи с нашими, мощных потоков зимних велосипедистов не наблюдается, см. Winter Cycling in Helsinki и Winter Cycling in Toronto

  • Тем не менее ездить в Питере кое-где можно, было бы желание. И еще должно пару раз повезти

  • Всем удачи и хорошего Нового года!

Примечания

  1. Впрочем, если считать на километр пробега, могут быть нюансы.

  2. Идея для стартапа - прозрачные вставки в капюшон.

  3. Тесть, увидев такое моё облачение, удивленно спросил, мол, как же ты педали-то крутишь?! На самом деле тут так. В статике ботинки уравновешивают друг друга, создавая равные по величине, но противоположно направленные моменты. В динамике, если не пользоваться тормозами, раскрученные ботинки играют роль механического аккумулятора, отдавая запасенную энергию при плавной остановке.

  4. К сожалению, несмотря на некоторые позитивные моменты, в целом велосипедное пространство в городе на Неве не является связным, и из целого ряда районов даже и не выехать нормально. Так что вся эта статья в определенном смысле есть "ошибка выжившего".

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru