Лечение спины и плеча

В прошлой статье, уже давней, я писал, что у меня была травма плеча, не помню когда, но точно с очень раннего детства. Рука при резких движениях и даже при некотором мышечном усилии выходила из плечевой сумки. Примерно как у Мела Гибсона в «Смертельном оружии». 🙂

В детстве я к этому относился несколько легкомысленно, иногда вынимая и вставляя руку в плечо. 🙂 Когда стал постарше, эта нетсабильность стала причинять определенные неудобства и временами болезненные ощущения. Например, если регулярно отжиматься — это приводило к продолжительной ноющей боли.

Один мой знакомый с подобной проблемой делал операцию в ННИИТО, что-то вроде укорачивания связок. У него рука при резких движениях выпадала из плечевой сумки. Например, рукой резко дернул и что-то вроде вывиха. Но мне подход с операцией показался черезчур радикальным. В какой-то момент я решил просто закачать мышцы плеча.

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

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

Другая проблема, которая была — боль в пояснице и ограничение в наклонах. Ограничение в наклонах вперед было с детства. Как минимум в школьном возрасте точно было. Видимо, какая-то особенность анатомии. При этом я почти садился на продольный и поперечный шпагат, но не мог наклонится вперед ниже чем под 90 градусов.

Когда в 2018 году, примерно, работал в Иннополисе, возможно, из-за сидячей работы, возможно, психосоматика, у меня очень сильно обострились боли в пояснице. Врач в больнице сказала что-то вроде: «Ну что вы хотите, сидячий, малоподвижный образ жизни и возраст. С годами будет только хуже, если ничего не измените в образе жизни.»

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

В общем, поясница почти вылечилась вместе с плечом с момента, когда серьезно занялся плаванием. Подготовка к заплыву через Волгу (3,5 км) была довольно интенсивной. Я занимался три раза в неделю по 45 минут, интенсивного плавания кролем. Проплывал поначалу от 1400 метров и дошел спустя год с небольшим до 2000 м. за 45 мин. Боли в пояснице почти прошли и с плечом почти все нормализовалось. Но, «почти».

В целом состояние стало нормальным. Примерно в 2020 под влиянием подруги, заядлой аштангистки :-), решил разнообразить лечение поясницы начав заниматься йогой. Прозанимался я примерно месяц. И мне было прям весьма плохо.

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

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

Поэтому вернулся к бегу. Бегал от 5 до 8 км, через день, зимой — лыжи и так примерно год сидения на карантине. Бегал в любое время года, в дождь и зимой. Затем открылся бассейн. Добавил плавание. В общем, йогу забросил полностью и возвращаться не то, чтобы планировал. Но проблема с поясницей и плечом полностью не ушла. Все равно побаливало.

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

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

В общем, я уже понял, что единственный способ — это серьезно занятся растяжкой и силовухой. Однако, занятия с «железом» мне никогда не нравились, не моё. Поэтому созрел, чтобы вернутся в аштангу. Мне нравится этот стиль йоги, поскольку аштангисты — весьма прокаченные ребятишки. 🙂 Нужно обладать очень хорошей выносливостью, чтобы выполнять асаны в аштанга йоге. Случился повод вернутся, приезд Маши Шалимовой, гуру аштанги. Посетить встречу с ней меня подбила подруга. 🙂

Чтобы не идти на встречу со йогасветилой с нулевыми познаниями в аштанге, я решил месяцок позаниматься с Леной Кальсиной. Вспомнить, как оно было раньше и посмотреть, что у меня с плечом и поясницей в статических позах аштанги. В общем, занялся где-то в феврале 2024 г. и вполне себе втянулся.

На семинаре было 3 дня майсора подряд и затем в пн. у меня был майсор у Лены. И вот он уже стал перебором для плеча. Разболелось прилично. Благо, я уезжал на пару недель в Египет и там не делал ничего, только очень лайтово плавал с рыбками в прохладной ещё мартовской водичке. Плечо практически полностью восстановилось.

В общем, плечо в йоге иногда травмируется, но я специально занимаюсь только по пн. и пт., чтобы у организма было достаточно времени на восстановление. Вт. и чт., а иногда ср. и чт. у меня бассейн, кроль 1800 м. за 45 мин. Такая комбинация позволяет телу нормально регенерировать. Лично для меня ни к чему эти аштангистские правила про занятия 6 раз в неделю. 🙂 Пусть молодежь наяривает. 🙂

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

Пота выходит очень много. Одно небольшое полотенце 40х80 см становится мокрым практически всё, поэтому инструктор посоветовала брать второе такое-же полотенце. Занимался поначалу с 6:30 до 8:15, примерно. После двух месяцев стал заниматься с 6:00 до 7:45 примерно. Я — жаворонок, поэтому такой режим мне очень комфортен.

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

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

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

Что ещё хочу отметить, занятия йогой, лично для меня — это сугубо групповой вариант занятий. Да, надо потратить время, чтобы дойти до зала и переодется, но по-другому не выходит.

У меня все довольно неплохо с силой воли. Я немало бегал вплоть до 30 км дистанций и при подготовке к забегам бывало набегал в выходной по полумарафону. И плавал по 1700-2000 м за 45 мин. при подготовке к заплывам. Самостоятельно, без тренера.

Но при этом я не могу заставить себя делать практику аштанги дома. Мне просто лень и жалко тело. 🙂 Одно дело, когда прессует инструктор, иногда больно, но термпимо — это мазохизм. Другое, когда нужно самому — это уже садомазохизм. 🙂

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

Рубрика: Здоровье, О жизни | Оставить комментарий

Обход ограничений на доступ к OpenAI API

Для работы нужен доступ к OpenAI API. Мне не хотелось искать способы временной регистрации зарубежных телефонов для получения SMS от OpenAI и оплаты сервиса по карте российских банков.

Я довольно давно пользовался сервисом https://chatgpt-accounts.ru/ для получения аккаунтов для доступа к API. Сервис проверенный, работает нормально, но только как-то зашел на него, а там только доступ к web интерфейсу OpenAI, а вот API нет.

Нашел сервис проксирования запросов к OpenAI API: https://proxyapi.ru/ Но в документации к сервису описан вызов через указание ключа и url прокси в аргументах конструктора.

from openai import OpenAI

client = OpenAI(
    api_key="{PROXY_API_KEY}",
    base_url="https://api.proxyapi.ru/openai/v1",
)

chat_completion = client.chat.completions.create(
    model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello world"}]
)

Мне нужен был способ передачи параметров через переменную окружения, поскольку нужна была векторная база FAISS и запускал я код в Colab. Нашел название переменной окружения:

from google.colab import userdata
os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY_PROXY')
os.environ["OPENAI_API_BASE"] = "https://api.proxyapi.ru/openai/v1" 

При использовании прокси сервера для доступа к API Proxy используется переменная окружения «OPENAI_PROXY»:

os.environ["OPENAI_PROXY"] = "http://your-corporate-proxy:8080"
Рубрика: IT рецепты | Метки: | Оставить комментарий

Распознавание голоса Tinkoff Voicekit

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

В Google Colab подгрузим нужные бибиотеки:

!pip install tinkoff-voicekit-client protobuf==3.20.3
!pip install pydub # установка библиотеки pydub

from pydub import AudioSegment

Посмотрим, что за параметры у загруженного аудиофайла:

# чтение из файла любого формата
music = AudioSegment.from_file(file='call.mp3', format='mp3')

print("Продолжительность аудио, сек:", music.duration_seconds)
print("Частота дискретизации:", music.frame_rate)
print("Количество каналов:", music.channels)

Результат:

Продолжительность аудио, сек: 90.0
Частота дискретизации: 16000
Количество каналов: 2

Обрежем первые 15 секунд аудио. На моей записи там гудки. Не влияет на распознавание. Можно и не делать.

# представление 15 секунд в миллисекундах
time_to_cut = 15 * 1000

# обрезка файла при помощи индексации
#music[time_to_cut:]
# сохраним фрагмент в файл с заданной миллисекунды
music[time_to_cut:].export('cutted.mp3', format='mp3')

Для использования сервиса Tinkoff VoiceKit необходима регистрация на платформе https://software.tinkoff.ru/auth/login/

После регистрации на балансе будет 1000 рублей, которые можно использовать для тестирования сервиса.

После регистрации необходимо создать и сохранить 2 ключа: API-key и SECRET-key.

  • API_KEY можно сгенерировать в личном кабинете в разделе VoiceKit в любой момент времени.
  • SECRET_KEY генерируется автоматически только при получении первого API_KEY, потом SECRET_KEY будет недоступен, поэтому крайне ВАЖНО сразу его сохранить.

Передаем параметры для авторизации:

import getpass

# передаем API_KEY
API_KEY = getpass.getpass("Tinkoff API Key:")
# передаем SECRET_KEY
SECRET_KEY = getpass.getpass("Tinkoff SECRET Key:")

Метод Recognize

Используется для распознавания аудиофайлов разных форматов (mp3, wav, s16). Для транскрибации текста метод принимает словарь параметров:

audio_config = {"encoding": "MPEG_AUDIO",  
                "sample_rate_hertz": 16000,
                "num_channels": 2}

"encoding" - кодировка, может быть: 'LINEAR16', 'ALAW', 'MULAW', 'LINEAR32F', 'RAW_OPUS', 'MPEG_AUDIO';
"sample_rate_hertz" - частота дискретизации записи;
"num_channels" - количество каналов записи (1 или 2).

Для распознавания речи используем следующий код:

from tinkoff_voicekit_client import ClientSTT
from pprint import pprint

# создаем клиент, передаем ключи
client = ClientSTT(API_KEY, SECRET_KEY)

# указываем параметры аудио
audio_config = {
    "encoding": "MPEG_AUDIO",
    "sample_rate_hertz": music.frame_rate,
    "num_channels": music.channels,
    "enable_automatic_punctuation": True,
    }

# вызываем метод recognize
response = client.recognize("cutted.mp3", audio_config)
pprint(response)

При распечатке данных возвращаемого объекта:

{'results': [{'alternatives': [{'confidence': -3.803578,
                                'transcript': 'Алло.',
                                'words': [{'confidence': 0.0,
                                           'end_time': '3.270s',
                                           'start_time': '3.060s',
                                           'word': 'алло'}]}],
              'channel': 1,
              'end_time': '3.270s',
              'start_time': '3.060s'},
             {'alternatives': [{'confidence': -4.384712,
                                'transcript': 'Здравствуйте, Алина.',
                                'words': [{'confidence': 0.0,
                                           'end_time': '4.320s',
                                           'start_time': '3.870s',
                                           'word': 'здравствуйте'},
                                          {'confidence': 0.0,
                                           'end_time': '4.710s',
                                           'start_time': '4.410s',
                                           'word': 'алина'}]}],
              'channel': 0,
              'end_time': '4.710s',
              'start_time': '3.870s'},
...
]}

В исходной записи звонок записан на два канала:

  • 0-й канал — то, что говорит менеджер.
  • 1-й канал — речь клиента.
for fragment in response['results']:
  alternatives = fragment['alternatives']
  for alternative in alternatives:
    str = ""
    if fragment['channel'] == 0:
      str += "Менеджер:"
    else:
      str += "Клиент:"
    str += " " + alternative["transcript"] + "\t[Start: " + fragment["start_time"] + ", End: " + fragment["end_time"] + "]"
    print(str)

Результат распознавания:

Клиент: Алло.	[Start: 3.060s, End: 3.270s]
Менеджер: Здравствуйте, Алина.	[Start: 3.870s, End: 4.710s]

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

def strTimeToInt(strtime):
  strtime = strtime.replace("s","")
  return int(float(strtime)*1000)

strTimeToInt("3.060s")

for fragment in response['results']:
  alternatives = fragment['alternatives']
  for alternative in alternatives:
    text = ""
    if fragment['channel'] == 0:
      text += "Менеджер:"
    else:
      text += "Клиент:"
    start_time = str(strTimeToInt(fragment["start_time"]))
    end_time = str(strTimeToInt(fragment["end_time"]))
    text += " " + alternative["transcript"] + "\tmusic[" +  start_time + ":" + end_time + "]"
    print(text)

Тогда получаем:

Клиент: Алло.	music[3060:3270]
Менеджер: Здравствуйте, Алина.	music[3870:4710]

Вставляя код

music[3060:3270]

в cell Colab получаем возможность прослушать выбранный фрагмент.

Рубрика: IT рецепты | Оставить комментарий

FastAPI на Colab через ngrok и uvicorn на Python

Столкнулся тут с проблемкой. Нужно было запустить FastAPI через uvicorn. При этом обращения с сервера должны были идти на API OpenAI. Код писал на Python в VSCode. Запустить надо было по-быстрому. Нормальных VPN не нашел. «Спасибо» добрякам из Роскомнадзора…

В общем, как самый быстрый способ проверки работоспособностии работы OpenAI API с FastAPI получился такой:

  • На ПК установлен Yandex Disk. На нем создана папка проекта.
  • В VSCode проект на Python сохраняется на Yandex Disk.
  • В Colab во первых строках кода исходники проекта скачиваются в локальную папку и добавляются в путь для поиска модулей.
  • Как скачать и развернуть файлы проекта в Google Colab с Yandex Disk описано в другой статье.
  • Подготавливается FastAPI.
  • Поднимается ngrok.
  • Запускается uvicorn.

Для подготовки FastAPI к запуску пример кода:

!pip install fastapi nest-asyncio pyngrok uvicorn

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=['*'],
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*'],
)

# класс с типами данных параметров 
class Item(BaseModel): 
    text: str

# функция обработки get запроса + декоратор 
@app.get("/")
async def read_root():
    return {"message": "answer"}

@app.get("/api/access_counter")
async def get_accesscouner():
  counter = chunk.get_accesscouner()
  return {"access_counter": counter}

# функция обработки post запроса + декоратор 
@app.post("/api/get_answer")
async def get_answer(question: Item):
    answer = chunk.get_answer(query=question.text)
    return {"message": answer}

Далее поднимаем ngrok. Передаем токен полученный при регистрации на Ngrok и предварительно сохраненый в .env. Подгружаем его через load_dotenv из папки загруженными с Yandex Disk исходниками:

  dotenv_path = "/content/" + dirs[0] + "/.env"
  load_dotenv(dotenv_path)

и запускаем веб сервер uvicorn:

import nest_asyncio
from pyngrok import ngrok
import uvicorn

ngrok.set_auth_token(os.environ.get("NGROK_AUTHTOKEN"))

ngrok_tunnel = ngrok.connect(8000)
print('Public URL:', ngrok_tunnel.public_url)
nest_asyncio.apply()
uvicorn.run(app, port=8000)

Сервер uvicorn повисает на временно сгенерированном ngrok внешнем домене.

Для бесплатных пользователей при создании аккаунта на Ngrok предоставляется один домен, который нужно создать в консоли ngrok (CloudEdge -> Domains). Выглядит он как-то так: «xxx.ngrok-free.app». Несколько доменов можно создать, купив платную подписку, но с проплатой из России проблемки. 🙂

Веб сервер Uvicorn туннелируется (пробрасывается) из Colab наружу через Ngrok и становится доступным для обращений по сгенерированному на Ngrok доменному имени. При обращениях по доменному имени запросы перенаправляются через Ngrok в Colab на Uvicorn и ответы возвращаются обратно.

ngrok_tunnel = ngrok.connect(8000, domain="your_domain.ngrok-free.app")

Возможно, есть достойные альтернативы ngrok, но времени на эксперименты не было.

Public URL: https://bb2f-34-16-145-243.ngrok-free.app
INFO:     Started server process [23974]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     81.222.179.30:0 - "GET /api/access_counter HTTP/1.1" 200 OK
INFO:     81.222.179.30:0 - "GET /api/access_counter HTTP/1.1" 200 OK
INFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [23974]

К сожалению, ячейка Colab запускается не асинхронно, т.е. управление с неё не передается на следующую ячейку. Гипотетически можно это обойти, запустив сервер через командную строку.

Для простоты я для тестирования использовал Postman. Вот так выглядит отправка запроса к серверу поднятому через ngrok на Colab.

Нюансы отправки REST API с помощью JavaScript через Ngrok

При отправке GET запросов на сервер через ngrok наверняка столкнетесь с тем, что стандартный JavaScript запрос выдает ошибку вида:

"Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON"

или

Unexpected token '<', \"<!DOCTYPE ";... is not valid JSON"

При этом POST запрос у меня почему-то проходил без вопросов. Я сломал всю голову, не понимая, что происходит, поскольку в браузере и в Postman возвращался нормальный JSON и только при запуске обращения к сервере через JavaScript возникала эта ошибка. Затем наткнулся на этот сервер: https://reqbin.com/post-online для проверки GET запросов. Подсунув туда URL на ngrok я получил в response HTML страницу, вместо JSON.

Странно, что при отправке запросов через Postman и другие сервера они всегда возвращали правильный JSON. Чтобы bypass ngrok browser warning нужно добавить в header строку:

'ngrok-skip-browser-warning': 'any'

Добавление нестандартного User-Agent, как указано в рекомендациях, проблему решает в https://reqbin.com/post-online, но при вызове в JavaScript возникает та-же ошибка. Рабочий JavaScript код для обхода ошибки:

&lt;script>
const url = 'https://xxx.ngrok-free.app/api/counter';
//const url = "https://jsonplaceholder.typicode.com/posts/1";
var headers = new Headers(
	{
        'User-Agent': 'PostmanRuntime/7.36.0',
        'Accept': 'application/json',
        'ngrok-skip-browser-warning': 'any',
        'Host': 'hugely-easy-pipefish.ngrok-free.app',
        'Content-Type': 'application/json'
  }
)
fetch(url, {
    method: "GET",
    headers: headers,  
    redirect: "follow"
  }
).then(response => {
    //console.log(response);
    return response.json();
  }
).then(data => {
    console.log(data);
  }  
).catch(error => {
    console.log(error.message);
  }
)
&lt;/script>

Запускать скрипт для отладки удобно в https://jsfiddle.net/. Если раскомментарить строчку console.log(response), то в консоли jsfiddle отобразится объект response со всеми свойствами и методами.

Рубрика: IT рецепты | Оставить комментарий

Скачивание данных с Yandex.Disk с помощью Python

При работе на Google Colab-е столкнулся с ситуацией, когда потребовалось скачать исходники разработанного модуля с Яндекс Диска (Yandex.Disk) на локальный диск Colab. Регистрация приложения для использования библиотеки YaDisk довольно небыстрая. Но можно сделать, если есть время. Ссылка.

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

При скачивании папки с Яндекс Диска в виде архива нужно извлечь папки. У меня все модули Python в одной папке, поэтому интересовала только корневая.

import zipfile, io, os, sys

#Извлекаем директории из архива
def getZipDirs(zip_f, isRootDirOnly = False, addPath = True):
  dirs = []
  for f in zip_f.namelist():
    zinfo = zip_f.getinfo(f)
    if zinfo.is_dir():
      if isRootDirOnly:     # Only root directories:
        # This is will work in any OS because the zip format specifies a forward slash.
        r_dir = f.split('/')
        r_dir = r_dir[0]
        if r_dir not in dirs:
          dirs.append(r_dir)
      else: # All directories:
          dirs.append(f)
  if addPath:
    for dir in dirs:
      sys.path.append(os.path.abspath('/content/' + dir + "/"))        
  return dirs

У функции скачивания файла с Yandex Disk два параметра:

  • url — путь к файлу или папке (например, https://disk.yandex.ru/d/rVVprjVVV2hS-w)
  • isZip — если передан путь к папке, то True, поскольку её содержимое вернется в Zip архиве. Если скачивание текстового файла, то False.

В этой функции архив сразу распаковывается на диск Colab и прописывается в путях для импортирования модуля.

import requests, sys

#url - путь к файлу или папке
#isZip - если передан путь к папке, то её содержимое вернется в Zip архиве
def loadTextFromYandexDisk(url: str, isZip = False):
  base_url = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?'
  final_url = base_url + urlencode(dict(public_key=url))
  response = requests.get(final_url)
  download_url = response.json()['href']
  download_response = requests.get(download_url)
 
  if isZip:
    zip = zipfile.ZipFile(io.BytesIO(download_response.content))
    dirs = getZipDirs(zip, True)
    zip.extractall("/content/")
  else:  
    download_response.encoding = download_response.apparent_encoding
    return download_response.text 

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

%load_ext autoreload
%autoreload 2 #Reload all modules (except those excluded by %aimport) every time before executing the Python code typed.
Рубрика: IT рецепты | Оставить комментарий

Соединение более 3-х силовых кабелей под клеммную колодку контактора/автомата

Иногда возникает необходимость под прижимной зажим контактора или автомата притянуть более 2-х силовых проводов. Например, в электрических котлах на каждую фазу может подключаться 3 реле (9 ступеней), соответственно, 3 провода надо завести под клеммник, что несколько противоречит требованиями ГОСТ/ПУЭ.

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

Самый простой вариант скоммутировать под одну клеммную колодку контактора/автомата три многожильных медных провода без нарушения ПУЭ/ГОСТ — использовать комбинацию концов НШВИ + НШВИ(2). При этом:

  1. П 3.4.7 ПУЭ «Присоединение двух медных жил кабеля под один винт не рекомендуется, а двух алюминиевых жил не допускается.». Материал коннектора у KBT: медь марки М1.
  2. Согласно ГОСТ 10434-82 «СОЕДИНЕНИЯ КОНТАКТНЫЕ ЭЛЕКТРИЧЕСКИЕ». 2.1.12. К каждому болту (винту) плоского вывода или к штыревому выводу рекомендуется присоединять НЕ БОЛЕЕ двух проводников, если иное не указано в стандартах или технических условиях на электротехнические устройства конкретных видов.
  3. Соответственно, оба стандарта НЕ РЕКОМЕНДУЮТ, но и НЕ ЗАПРЕЩАЮТ соединять два МЕДНЫХ кабеля.
  4. НШВИ(2) — это стандартный наконечник под два провода. Возможно, с этим и есть какие-то проблемы в части надежности такого способа соединения, но он вполне допустим.
  5. «К каждому болту (винту) плоского вывода или к штыревому выводу рекомендуется присоединять не более двух проводников». Многожильный проводник при заведении под клеммную колодку контактора, по-правилам, гильзуется, в т.ч. НШВИ. Соответственно, не запрещено вводить два НШВИ+НШВИ, хоть и не рекомендуется.
  6. При заведении вместо двух НШВИ+НШВИ двух наконечников НШВИ + НШВИ(2) под пластину клеммной колодки контактора вполне допустимо.

Однако, есть риск, что при опрессовке НШВИ и НШВИ(2) высоты опрессованных концов могут немного отличаться. При прижатии планкой может возникнуть чуть более худший контакт у одного из проводников и разогрев на больших токах в этом месте. Но это гипотеза. Она ровно также может быть при опрессовке двух концов НШВИ + НШВИ. 

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

При таком соединении 3-х проводов к каждой клемме контактора получается ВСЕГО!!! 3-и винтовых соединения по два конца НШВИ + НШВИ(2). Причем, в зависимости от типа реализации способа прижатия конца провода в контакторе, риски из-за разной толщины могут нивелироваться.

Соотвественно, такое решение:

  1. Не запрещено ГОСТ/ПУЭ, хоть и не является рекомендованным. 
  2. Минимум винтовых соединений, соответственно в разы сокращается брак.
  3. Очень дешевое. Переход на клеммные сборки — это приличный рост себестоимости готового изделия.
  4. Достаточно надежное при правильном исполнении: обжимка и затяжка.
  5. Наименее трудозатратное при сборке.
  6. Очень компактное.
  7. Проверенное годами эксплуатации. Брак из-за сборки минимальный. В ходе эксплуатции решение хорошо себя зарекомендовало при качественном контакторе, когда не экономят на меди и серебре.

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

  1. Есть 3 винта на контакторе + 3 винта на переход кабеля от контактора на клеммную сборку + 3 винта под каждый провод на одной фазе. Итого получаем: 3 + 3 + 3 * 3 фазы = 15 винтов.
  2. Кол-ва брака возрастает в несколько раз, поскольку на каждой затяжке винта увеличается риск получения нагрева.
  3. Хотя здесь нагрев будет другим. Он будет распределятся по значительно большей площади металла.
  4. Трудозатраты при сборке возрастают в 5 раз.

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

Соответственно, имеем разогрев контакта контактора при срабатывании. Тепло от него по медной пластине передается на клеммник. К этому добавляется ещё разогрев клеммника контактора если плохо поджаты НШВИ. Эти две генерации тепла находятся близко друг от друга, увеличивая температуру разогрева, при малой площади теплоотведения.

На дорогих и качественных контакторах такого не происходит:

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

Чтобы нивелировать проблемы при некачественном контакторе напрашивается простое и дешевое решение — использовать повышенное сечение жилы кабеля подводимого к контактору. Например, если расчетное сечение 2,5 мм2, то использовать 4 мм2. С увеличением диаметра жилы увеличится и диаметр гильзы НШВИ. В совокупности это позволит:

  • Увеличить площадь контакта за счет большеей площади опрессованного наконечника.
  • Лучше отводить тепло от контакта контактора за счет большей площади сечения провода.
  • Распределять тепло более равномерно, чтобы не допускать перегревания изоляции.  

Клеммники на DIN рейку

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

  1. Если на контакторе только один НШВИ, контакт, несомненно, будет лучше из-за отсутствия перепада высот на двух наконечниках.
  2. Уменьшается риск неподжатия контактом клеммы контактора наконечника НШВИ. Это приведет к снижению разогрева.
  3. Кабель от контактора до клеммника рассеивает тепло. Его диаметр нужно брать с запасом. Например, вместо расчетных 2,5 мм2 брать 4 мм2, чтобы отводить тепло от контактора максимально эффективно.
  4. Появляется металл клеммника к которому приходит кабель от контактора, дополнительно рассеивая тепло. До него тепло вряд ли дойдет, но все-же…

Остается вопрос что делать с клеммными блоками. Если ставить качественные клеммные блоки, то их нужно много, поскольку только на клеммный блок с болтовым соединением можно завести два провода. Например, на такой https://youtu.be/XD5f-v9AjQQ?si=OS2nZ6PW_OKD-Dyi.

Остальные типы фиксиации в клеммныом блоке не позволят ввести два провода в одно гнездо. Хотя некоторые клеммники позволяют заводить по два провода с каждой стороны, но тогда, как правило, увеличивается количество винтовых соединений. Придется либо использовать НШВИ(2), либо увеличивать количество клеммных блоков, соединяя их заводской перемычкой. Итого получаем:

  • Нужны секции клемников, которые допускают установку перемычки.
  • Тогда на каждую фазу нам нужно две секции и перемычка на два клеммника.
  • Клеммник должен позволять заводить кабель 4 мм2 и 2,5 мм2, либо придется ставить два разных клеммника.
  • Соединяем два клеммника друг с другом заводской перемычкой.
  • Заводим один провод 4 мм2 от контактора.
  • Заводим 3 провода 2,5 мм2 в три оставшихся гнезда клеммника.
  • Итого на каждую фазу 6 секций клеммника и 3 перемычки на 2.
  • Ценник будет приличный.

Примерно вот так это выглядит на котле Lemax Pro Plus:

Можно использовать недорогой клеммник с болтовым соединением и гроверной шайбой для исключения разбалтывания от вибрации. Он существенно более трудозатратный при сборке, однако обеспечивает лучший контакт. Подгорать будет (если будет) более дешевый клеммник, а не контактор. Клеммник можно взять, например, такой:

Решение также не идеальное:

  1. С каждой стороны клеммы придется под болт загонять по 2 провода. Это будет также не рекомендуемым, но допустимым вариантом.
  2. Вот здесь показано, что до 2-х концов можно использовать https://youtu.be/XD5f-v9AjQQ?si=OS2nZ6PW_OKD-Dyi и это рекомендации от Феникса.
  3. Соответственно оконцовывать надо вот такими концами, поскольку U-образные не позволят обеспечить качественный контакт при накладывании друг на друга.
  4. Это, несомненно, более трудозатратный вариант при сборке, чем ввести провод в контактор и затянуть динамометрической отверткой.

Наконечники кольцевые изолированные с ПВХ манжетой

Хотя есть более компактное решение под тип наконечников нередко используемых на силовых реле. Так называемый «tab connector terminal block«. Он более «плотный» для компоновки.

Tab connector terminal block

Интересный вариант плотного клеммника с хорошей изоляцией, т.е. минимальным риском для монтажника, на DIN рейку с возможностью установки перемычки CTC4U (каталог):

Под вилки РПИ-М. Вроде такой:

Есть ещё вот такие power distubution blocks http://www.blox-electric.com/list/post/2385969/, но таких надо 3 шт. на каждую фазу и стоят они в районе 250 руб. При габаритах 66x27x46 займет немало места.

Насколько значительно улучшается качество контакта/снижается уровень брака в процессе эксплуатции при использовании дополнительных клеммных блоков сказать сложно. Но точно увеличивается себестоимость и увеличивается место под дополнительные компоненты.

Рубрика: Лайфхаки | Оставить комментарий

Проверка в AimyLogic создания дубликата записи смарт-процесса в Битрикс (REST API)

В продолжение другой статьи по теме интеграции Aimylogic c Битрикс рассмотрим как реализовать следующий алгоритм:

  • Клиент звонит и попадает на голосовой бот. Оставляет заявку в ServiceDesk (например, Битрикс).
  • Время обработки сервисным инженером заявки, созданной голосовым ботом, например, 2 дня.
  • Нетерпеливый пользователь, не дождавшись звонка от инженера, повторно звонит и попадает на голосового бота Aimylogic.
  • Голосовой бот должен посмотреть, за последнее время были ли созданы заявки от пользователя с того-же номера.
  • Если такие заявки есть, то голосовой бот должен успокоить клиента, сообщив, что в системе уже есть заявка. Звонок связан с новой проблемой или с ранее зарегистрированной.
  • Если клиент озвучит, что проблема другая, то должна создастся новая заявка.

Получение контакта клиента в Bitrix через REST API

Итак, Aimylogic определил с какого номера пришел звонок и ему нужно по этому номеру определить сервисные заявки для которых указан контакт с этим номером. Для определения есть ли клиент с указанным номером $client_phone в базе контактов Битриска используется следующий запрос:

https://xxxxxxxx.ru/rest/177/xxxxxxxxxxxx/crm.duplicate.findbycomm.json

{
"entity_type": "CONTACT",
"type": "PHONE",
"values": [ "$client_phone" ]
}

В ответе берем переменную $httpResponse.result в которой передается ID контакта.

Напомню, что добавляли смарт-процесс используя следующий POST запрос:

https://xxxxxxxxxxx.ru/rest/177/xxxxxxxxxxxxxxx/crm.item.add.json

{
"entityTypeId": "158",
"fields": {
"SOURCE_ID": "14",
"contactId": "$contact_id",
"ufCrm3_1680255422": "$equipment", 
"ufCrm3_1680254883": "$client_city",
"ufCrm3_1680261235": "$question",
"ufCrm3_1687168305": "$mobilePhone", 
"ufCrm3_1680261458": "$productiondate",
"ufCrm3_1697115862": "$equipment_state"
}
}

Соответственно, нам надо получить список смарт-процессов «сервисная заявка» у которых в поле «contactId» прописан полученный ранее ID контакта. Протестируем запрос в Postman на примере контакта с известным номером мобильного телефона. Получим несколько ID по номеру:

{
    "result": {
        "CONTACT": [
            14116,
            14118
        ]
    },
    "time": {
        "start": 1699808317.849979,
        "finish": 1699808317.890918,
        "duration": 0.0409390926361084,
        "processing": 0.004055976867675781,
        "date_start": "2023-11-12T19:58:37+03:00",
        "date_finish": "2023-11-12T19:58:37+03:00"
    }
}

Видим, что с указанным номером в Битриксе есть два контакта. Контакты были созданы через разные источники. Например, через форму обратной связи и обращение через голосового бота. Считаем, что это аномалия, которую надо устранить дополнительной проверкой, чтобы исключить дублирование при создании контактов с одним номером телефона.

Посмотрим как выглядит ответ для номера у которого точно один контакт в Битриксе:

{
    "result": {
        "CONTACT": [
            14111
        ]
    },
    "time": {
        "start": 1699809721.426188,
        "finish": 1699809721.46897,
        "duration": 0.04278206825256348,
        "processing": 0.005627155303955078,
        "date_start": "2023-11-12T20:22:01+03:00",
        "date_finish": "2023-11-12T20:22:01+03:00"
    }
}

Тот-же массив «CONTACT», т.е. нам достаточно в Aimylogic взять первый элемент в этом массиве.

function getContact(contacts) {
    
    try {
        $obj = JSON.parse(contacts);

        if ("CONTACT" in $obj) {
            if ($obj.CONTACT.length > 0)
            {
                $contact = $obj.CONTACT[0];
            }
        }
    } catch (e) {
        return -1   
    }
    return $contact
}

$contact = getContact($contacts)

Получение сервисной заявки (смарт-процесс) клиента в Bitrix через REST API

Чтобы получить сервисную заявку к которой привязан найденный контакт воспользуемся методом crm.item.list:

https://xxxxxxxxx.ru/rest/177/xxxxxxxxxxxxx/crm.item.list.json 

{
    "entityTypeId": 158,
    "filter": {
        "=contactId": "14111"
    }
}

После выполнения запроса к Битриксу получаем JSON в котором возвращается найденная сервисная заявка со всеми нужными нам полями. Полей много, поэтому заменил на …….

{
    "result": {
        "items": [
            {
                "id": 2214,
                "title": "Сервисная заявка #2214",
                "contactId": 14111,
........
                "entityTypeId": 158
            }
        ]
    },
    "total": 1,
    "time": {
        "start": 1699811918.669397,
        "finish": 1699811918.71892,
        "duration": 0.04952287673950195,
        "processing": 0.021754980087280273,
        "date_start": "2023-11-12T20:58:38+03:00",
        "date_finish": "2023-11-12T20:58:38+03:00"
    }
}

Чтобы сократить нагрузку на сервер и сократить трафик, можно выбирать в запросе только нужные поля:

{
    "entityTypeId": 158,
    "select": ["contactId", "stageId", "id", "createdTime"],
    "filter": {
        "=contactId": "14111" 
    }
}

Соответственно, создаем в AimyLogic HTTP запрос POST к методу crm.item.list.json и в body прописываем:

{
    "entityTypeId": 158,
    "filter": {
        "=contactId": "$contact"
    }
}

Результат $httpResponse.result помещаем в переменную $items. И далее обрабатываем полученный JSON следующим образом:

function getItem(items) {
    try {
        $obj = JSON.parse(items);

        if ($obj.items) {
            if ($obj.items.length > 0)
            {
                $item = $obj.items[0];
            }
        }
    } catch (e) {
        return -1
    }
    return $item
}

$item = getItem($items);
$item_created = -1;
if ($item != -1) {
    $item_id = $item.id;
    $item_created = $item.createdTime;
}

Все отрабатывает нормально. Получаем сервисную заявку и берем дату создания.

Определение срока давности сервисной заявки

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

function getDateDifference(isoDateStr)
{
    try {
        $date1 = new Date(isoDateStr);
        $date2 = new Date();
        return ($date2.getTime() - $date1.getTime()) / (1000 * 3600 * 24); 
    } catch (e) {
        return Number.MAX_VALUE;
    }
}

if ($item_created != -1) {
    $daysdiff = getDateDifference($item_created);
}

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

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

{
    "filter": {
        ">createdTime":"2020-03-19T02:00:00+02:00"
    }
} 

Пример финального запроса будет выглядеть следующим образом:

https://xxxxxxxxx.ru/rest/177/xxxxxxxxxxxxx/crm.item.list.json 

{
    "entityTypeId": 158,
    "select": ["contactId", "stageId", "id", "createdTime"],
    "filter": {
        "=contactId": "14111",
        ">createdTime": "2023-11-11T15:08:13+03:00" 
    }
}

Итак, после обработки полученной даты создания сервисной заявки получили разность дат в днях и можно принимать решения, озвучить клиенту, что он недавно создал заявку и она в обработке или позволить создать новую заявку. Можно дополнительно уточнить у клиента, обращение по старой заявке или какая-то новая проблема.

В этот запрос надо ещё добавить поиск только тех заявок у которых статус stageId удовлетворяет необходимым. Например, если stageId — SUCCESS, при этом дата создания заявки менее n дней назад, то:

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

Поскольку здесь действия вариативные, нельзя включать в предфильтрацию stageId и фильтровать, например, только заявки у которых состояние SUCCESS.

Определение даты создания самой свежей сервисной заявки

Теперь нужно учесть момент, что у клиента с одним и тем-же номером телефона, но, например, несколькими контактами в Битриксе, может быть несколько сервисных заявок созданных в разное время. Соответственно, нужно пройтись по всем его заявкам и найти самую свежую. Гипотетически, если заявка была создана относительно недавно (в пределах 2-3 дней), то пользователь перезванивает по ней, поскольку сервиcные инженеры ещё не дошли.

После выполнения HTTP запроса на получение списка контактов, конвертируем в массив $contacts.

function getContact(contacts) {
    
    try {
        $obj = JSON.parse(contacts);

        if ("CONTACT" in $obj) {
            if ($obj.CONTACT.length > 0)
            {
                return $obj.CONTACT;
            }
        }
    } catch (e) {
        return -1   
    }
}

$contacts = getContact($contacts);

Склеиваем элементы массива в строку для передачи в HTTP POST запрос. Из id контактов должно получится что-то вроде: [«14116», «14118», «14142»]

//Функция склеивает массив в строку для POST запроса. Например, ["14116", "14118", "14142"]
function getContactsList(contacts)
{
    try {
        return "[\"" + contacts.join("\", \"") + "\"]";
    } catch (e)
    {
        return "[]";
    }
}

$contacts_str = getContactsList($contacts)

Теперь подаем результат в HTTP POST запрос Aimylogic.

{
    "entityTypeId": 158,
    "select": ["contactId", "stageId", "id", "createdTime"],
    "filter": {
        "=contactId": "$contacts_str",
        ">createdTime": "$minStartDate" 
    }
}

После выполнения запроса в переменной $items = $httpResponse.result будет JSON с массивом ВСЕХ сервисных заявок, которые привязаны к ВСЕМ номеру телефона клиента.

Ну и далее перебираем все даты создания сервисных заявок и находим сервисную заявку с наиболее близкой датой.

function getNewestItem(items) {
    try {
        $obj = JSON.parse(items);
        $lastDate = new Date(1975, 10, 16); //Инициализируем произвольной датой в прошлом. 

        $newest_item = -1;
        if ($obj.items) {
            if ($obj.items.length > 0)
            {
                for ($i = 0; $i &lt; $obj.items.length; $i++) {
                    try
                    {
                        $item = $obj.items[$i];
                        $item_currentDate = new Date($item.createdTime);
                        if ($item_currentDate > $lastDate)
                        {
                            $lastDate = $item_currentDate;
                            $newest_item = $item;
                        }
                    } catch (e) {                    
                    } 
                }
                return $newest_item;
            }
        }
        return -1;
    } catch (e) {
        return -1;
    }
}

$item = getNewestItem($items)

Высчитываем разность между датами, как делали это раньше. Для диагностики при отладке можно вывести информацию в блоке «Синтез речи»:

if ($item != -1) {
    $item_created = $item.createdTime;
    $date1 = new Date($item_created);
    $date2 = new Date();
    $daysdiff = getDateDifference($item_created);
}

Далее алгоритм Aimylogic затачиваем под проверку разницы дат и дополнительным вопросам, а уверен ли пользователь, что это не повторная заявка.

Рубрика: IT рецепты | Оставить комментарий