четверг, 22 февраля 2024 г.

SQL учет простоев

У нас на работе уже давно сделана такая система учета простоя, и хотелось бы оставить заметку чтобы если придется повторять нечто подобное было проще разобраться. Смысл такой что каждую секунду или две на SQL сервер из контроллера передается сигнал по UDP протоколу, в котором описано состояние агрегата - скорость его, длина и номер рулонов, режим работы механизмов и тд. Данные о состоянии пишутся в SQL таблицу, и обрабатываются триггером каждый раз (раз в секунду или две), где сравнивается предыдущее состояние и текущее, и если видно что агрегат остановился, то начинается отсчет простоя - в другой таблице отбивается время начала простоя. Как только триггер фиксирует обратное изменение, то есть видно что секунду назад агрегат еще стоял, а сейчас уже едет - то в таблице простоя отбивается время окончание простоя. Таблица простоя имеет такую структуру:

При этом данные в таблицу по UDP протоколу пишутся в бинарном формате, и выглядит это примерно так: 

Триггер обрабатывающий бинарную строку состояния агрегата находится в гитхабе моем гитхабе https://github.com/boolkin/scripts/blob/master/SQL/AOM/%D1%82%D1%80%D0%B8%D0%B3%D0%B3%D0%B5%D1%80%20unscale.txt
Там происходит расшифровка данных и как раз та самая логика по проверке предыдущего состояния и нынешнего, и в зависимости от этого строка добавляется или обновляется. 
так же там есть триггер который обрабатывает выведение турбин из работы: аналогично если число показывающее количество турбин в работе изменилось, то если оно уменьшилось то добавляется номер турбины в столбец выведения, а если увеличилось то в столбец введения в работу. плюс затем еще и переименовывается номер в человеческое имя. 
Наиболее интересно было попробовать заюзать цикл: необходимо было посчитать время которое турбина была выведена из работы - алгоритм такой что ищем название этой турбины в столбце выведения, затем ищем его в столбце введения - считаем время этого промежутка, продолжаем поиск по этому же алгоритму и прибавляем ко времени промежутка следующий - это все было добавлено в скалярную функцию и затем получилось выводить в виде таблицы отчета.

суббота, 10 февраля 2024 г.

Как удаленно запретить пользователюудалять файлы

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

@echo off
icacls "D:\foldername" /deny UserName:(OI)(CI)(DE,DC) /T /C
Причем в названии папке в конце слэш не нужен. В данном случае появляется запрет на удаление этой папки и всех файлов и подпапок внутри нее, все-равно что если бы сделали это вручную через проводник Windows (т.к.лень делать много скринов все на одном):

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


Чтобы убрать запрет можно удалить вот этот элемент разрешений через проводник, либо применить такой же самый батник, но вместо /deny поставить /grant:

@echo off
icacls "D:\foldername" /grant UserName:(OI)(CI)(DE,DC) /T /C

Понятное дело что UserName нужно ввести свой, как и путь до папки

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

 wmic /user:UserName /node:"ip-remote-pc" process call create "C:\scripts\deny.bat"

Как можно догататься в конце прописан путь до ранее созданных нами батников на компе где требуется установить запрет (deny)

Скорее всего при этом система ругнется что отказано в доступе и тогда может помочь эта инструкция: https://serveradmin.ru/nastroit-udalennyj-dostup-po-wmi/

В ней написано что нужно установить частную сеть и запустить команду winrm quickconfig

воскресенье, 15 октября 2023 г.

Преобразование строки в массив чисел float

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

    string answer = req.ResponseText;
    answer = answer.Replace("&", ";");
    float[] strarr = new float[35];
    for (int i = 0; i< 35; i++)
    {
        int position = answer.IndexOf(";");
        string str = answer.Substring(0,position);
        strarr [i] = float.Parse(str, CultureInfo.InvariantCulture.NumberFormat);
        answer = answer.Substring(position+1);
    }

Для этого добавил счетчик элементов по количеству встречаемых символов ";" и чтобы не добавлять пустую строку в конце сделал еще и условие позиции этого символа:

    string answer = req.ResponseText;
    answer = answer.Replace("&", ";");
    int count = 0;
    foreach (char c in answer) if (c ==';') count++;
    float[] strarr = new float[count+1];
    int position = 0;
    int i = 0;
    do
    {
        position = answer.IndexOf(";");
        if (position>0) {
            string str = answer.Substring(0,position);
            strarr [i] = float.Parse(str, CultureInfo.InvariantCulture.NumberFormat);
            answer = answer.Substring(position+1);
            i++;
        }          
    }
    while (position>0);

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

    string answer = req.ResponseText;
    answer = answer.Replace("&", ";");
    string[] strarr = answer.Split(';');
    float[] floarr = new float[strarr.Length];
    if (strarr.Length > 1)
    {
        for (int i = 0; i < strarr.Length-1; i++)
        {
            floarr[i] = float.Parse(strarr[i], CultureInfo.InvariantCulture.NumberFormat);
        }
    }

Кстати этот код можно упростить удалив строку с Replace, а все потому что метод Split может разделять строку по нескольким разделителям, то есть нужно задать там ; и & чтобы разделить на массив. Также этот код можно упростить используя Array.ConvertAll, но я так долго разбирался с тем почему происходит ошибка даже на таком элементарном коде который выше, что решил что ну его нафиг, пусть остается так, лучше напишу просто заметку об этом и скину ссылку как еще можно преобразовать массив. Вообще было бы еще интересно сравнить какое преобразование оптимальнее, чтобы точно убедиться что лучше)), хотя оба кода работают нормально вцелом. Хотя опять же - нет никакой валидации на входе - если компрессор место числа выдаст какой-липо символ и пустоту, то в очередной раз будет ошибка и будет необходимость переписывать код...

PS не поленился и в онлайн режиме (на сайте майкрософт с описанием метода Split можно включить проверку кода) сделал компиляцию предыдущего случая и упрощенного до 3 строк кода, причем последний пустой элемент массива удаляется автоматически, используя перегрузку метода Split, о чем говорилось и в статье майкрософт:

    string answer = req.ResponseText;
    string[] strarr = answer.Split(new char[] { ';', '&' }, StringSplitOptions.RemoveEmptyEntries);
    float[] floarr = Array.ConvertAll(strarr, s => float.Parse(s, CultureInfo.InvariantCulture.NumberFormat));
Вот и очередной лайфхак, как все это дело проверять - можно проверять онлайн, а редактором для блога использовать онлайн vscode))) отличная работа)
Чтобы проверить можно сделать так :
    string answer = "95.460999;01;65.000000;99.811996&109.811996;01;75.000000";
    string[] strarr = answer.Split(new char[] { ';', '&' }, StringSplitOptions.RemoveEmptyEntries);
    float[] floarr = Array.ConvertAll(strarr, s => float.Parse(s, CultureInfo.InvariantCulture.NumberFormat));
    foreach (var sub in floarr)
    {
    Console.WriteLine($"Substring: {sub}");
    }

вторник, 19 сентября 2023 г.

Сделал приложение для управление "умным" домом через яндекс станцию

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

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

вторник, 29 августа 2023 г.

Отчеты в IBA PDA

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

Хотел бы обратить внимание на самую верхнюю формулу - по ней берется не все загруженное время, а лишь последние 12 часов 10 минут (43200 сек + 600 сек), для чего берется последняя метка времени, и от нее отнимается заданное время.

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

С редактором лейаута довольно просто разобраться ,потому как работает все по принципу drag and drop - нужно перенести необходимые компоненты из правой части окна в окно лейута 

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

воскресенье, 23 июля 2023 г.

Формулы анализа IBA PDA

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

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


А тут видно как погрешность возрастает, потому как на валу идет проскальзывание полосы:

и формулы:

Ну и еще раз формула вычисления продолжительности сигнала в секундах в текстовом виде:
ToText ((MaxValid ( XValues ([x]) , [x] ) -MinValid ( XValues ([x]) , [x] ) ),"%g")

пятница, 21 июля 2023 г.

CMD передать аргуметом файлы с определнной датой

Я уже и говорил в предыдущей записи почему все это началось - по работе появилась необходимость автоматизации рутинного процесса. Смысл был такой, что есть некая программа, способная выводить графики процессов агрегата за определенный период времени. Начальство требует этот график в конце каждой смены: открываешь программу, добавляешь в нее файлы за смену (несколько штук, причем количество файлов может варьироваться), добавляешь правило анализа, жмешь кнопку print, печатаешь в pdf и отправляешь этот файл по email. Если бы мастер мог сам все это делать (но у них нет желания и способностей к обучению как мне кажется), то я бы даже не заморачивался, но поскольку приходится самому, то мне стало интересно потратить 12 часов рабочего времени на автоматизацию работы, которая занимает 5 минут:

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

Перво наперво, нужно понять из какой папки забирать файлы, а это папка с именем текущей даты в формате yyMMDD. Как это сделать разобрались еще в прошлой заметке. Теперь можно открыть эту папку (команда cd %folder%) и получbть список файлов, который особым методом отправляется в массив:

setlocal disableDelayedExpansion
:: Load the file path "array"
for /f "tokens=1* delims=:" %%A in ('dir /s /b^|findstr /n "^"') do (
  set "file.%%A=%%B"
  set "file.count=%%A"
)

конструкция расшифровывается примерно так: если ввести просто команду dir /s /b то будет возвращен обычный список из полных имен файлов, который включает в себя и полный путь. Соответственно вся эта конструкция осуществляет поиск в это списке эти пути, и отправляет в массив file. Синтаксис до сих пор не понятен, но зато после чтения массива, можно обратиться к каждому файлу и прочитать его время и дату изменения, и если оно совпадает с тем что требуется, то добавлем его в общую строку %concat% которую позднее скормим нашей программе для вывода графиков:

:: Access the values
setlocal enableDelayedExpansion
FOR /l %%N in (1 1 %file.count%) do (
    FOR %%? IN ("!file.%%N!") DO set filedate=%%~t?
    set day=!filedate:~0,2!
    set hour=!filedate:~11,2!
    set yday=!yest:~4,2!
    if !day! EQU %date:~0,2% SET concat=!concat! "!file.%%N!"
    if !day! EQU !yday! if !hour! GTR 19 (
        SET concat=!concat! "!file.%%N!"
    echo H:!hour! y:!yday!
    )
)
Итак, первый FOR читает массив, второй FOR для каждого файла из массива отправляет в переменную %filedate% его время и дату создания. Причем опять же синтаксис маскимально непонятный - конструкция тупо скопированна из примера, например, почему именно %%~t? нужно для этого мне не понятно. Более того, как выяснилось, переменные для вывода значений за пределом массива обрамляются процентами %%, а внутри массива восклицательными знаками !! - зачем именно так, тоже странно. Как и говорилось в предыдущей заметке, знак с тильдой указывает место (начиная с нуля) и количество символов, которые будут отправляться в переменную day, hour, yday (вчерашний день).

Далее день сравнивается с текущим днем, и если они равны, то в общую строку %concat% отправляется путь до этого файла. Дополнительное условие сравнивающее время создания файла со вчерашним днем и часом создания с 19 необходимо по той причине что за ночную смену, файлы раскиданы по разным папкам, поэтому приходится делать проверки дважды, после смены папки на вчерашний день (по факту проверяется сначала вчерашний день, а затем уже день сегодняшний). Делается это при помощи меток и метода goto. После того как все нужные файлы добавлены в общую строку, ее можно отправить в качестве аргумента для запуска программы графиков:

set string="C:\Program Files\thisProgram\program.exe"%concat% C:\dat\cgl-b4.pdo /append /report:C:\dat\CGL_%date%.pdf
echo %string%
%string%

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

@echo off
SET folder=C:\datfiles\%date:~8,2%%date:~3,2%%date:~0,2%
for /f "tokens=*" %%a in ('PowerShell -Command "& {Get-Date (Get-Date).addDays(-1) -format "yyMMdd"}"') do set yest=%%a
set cycle=0
cd C:\datfiles\%yest%
:M1
setlocal disableDelayedExpansion
:: Load the file path "array"
for /f "tokens=1* delims=:" %%A in ('dir /s /b^|findstr /n "^"') do (
  set "file.%%A=%%B"
  set "file.count=%%A"
)

:: Access the values
setlocal enableDelayedExpansion
FOR /l %%N in (1 1 %file.count%) do (
    
    FOR %%? IN ("!file.%%N!") DO set filedate=%%~t?
    set day=!filedate:~0,2!
    set hour=!filedate:~11,2!
    set yday=!yest:~4,2!
    if !day! EQU %date:~0,2% SET concat=!concat! "!file.%%N!"
    if !day! EQU !yday! if !hour! GTR 19 (
        SET concat=!concat! "!file.%%N!"
    echo H:!hour! y:!yday!
    )
)

cd %folder%
set /A cycle=cycle+1
echo cnumb__%cycle%
if %cycle% LEQ 1 goto M1
set string="C:\Program Files\thisProgram\program.exe"%concat% C:\dat\cgl-b4.pdo /append /report:C:\dat\CGL_%date%.pdf
echo %string%
%string%