// Создать векторную шкалу времени в зависимости от смены
// 1 аргумент - класс
// 2 аргумент - время начала смены
// generateTimeline("class", 20);
//h графикa = 94,864%, и отступить от левого края 12px
function generateTimeline(c, start){
var elements = document.getElementsByClassName(c);
var e;
for(let ii=0; ii<elements.length; ii++){
e = elements[ii];
var str = '<line y1=40% x1=1px y2=40% x2=94.864% style=\'stroke:black;stroke-width:0.2%\' />';
var text, res;
var all_h = e.getBoundingClientRect().height;
var all_w = e.getBoundingClientRect().width;
var textsize = all_h/2;
for(var i=0;i<=6;i++){
// Делим на 12 частей
let t = i*7.9*2;
let s = t;
let timeText = i*2 + start;
if(timeText > 23) timeText = i*2 - 4;
str = str + '<line y1=40% x1=' +s+ '% y2=0% x2=' +s+ '% style=\'stroke:black;stroke-width:0.3%\' />';
text = text + "<text y=100% x=" +t+ "% class=\'small\'>" +timeText+ ":00</text>";
}
res = "<svg style = 'width:100%;height:100%;margin-left: 12px;' >" +str+ "</svg>";
var ret = "<svg width = 100% height = 100%>" +text+ "</svg>";
var d = "<div style = 'width:100%;height:100%;display:grid'>"+res+ret+"</div>";
e.innerHTML = d;
}
}
В приложении файл для экспорта приложения, анализирующее работу операторов.
1. Должен быть подключен сигнал с именем оператора. Указать сигнал можно в коде.
2. Данные берутся из приложения 1 - Работа под нагрузкой.
3. Приложение работает с сигналами, поэтому при выборе длительного периода формирование отчета занимает время, но все равно строится.
4. При построении таблиц используется встроенная функция baseUtils.createTable
Изменяя параметры в функции createTable можно изменить внешний вид таблицы.
КПД - это параметр, характеризующий загруженность оператора на конкретном станке - определяется временем регистрации, деленном на время выполнения программы станка.
Во вложении пример приложения с использованием chart.js v4 и дополнительных плагинов для графика работы оборудования. Для версии платформы 5.4.5 и выше.
Недавно в WINNUM Platform появился новый функционал - бюллетени, в следствии чего захотелось его дополнить.
Ниже представлен код на C# с использованием .NET 6 и библиотекой Telegram.Bot 18.0.0, который позволяет на любой сгенерированный бот в telegram отправлять последнюю бюллетень полученную от publisher:
Код:
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Exceptions;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.InputFiles;
using Telegram.Bot.Types.ReplyMarkups;
class PublisherBot
{
private static ITelegramBotClient botClient;
var keyboard = new InlineKeyboardMarkup(new[] { new[] { button } });
await botClient.SendTextMessageAsync(chatId, "Нажмите кнопку ниже, чтобы получить последний бюллетень:", replyMarkup: keyboard);
}
static async Task SendLatestPdfFileAsync(long chatId)
{
string directoryPath = @"C:\\Winnum\\WinnumPlatform\\publisher\\cache\\temp"; // Путь до temp у publisher
var latestPdfFile = GetLatestPdfFile(directoryPath);
if (latestPdfFile != null)
{
try
{
using var stream = new FileStream(latestPdfFile.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
var inputOnlineFile = new InputOnlineFile(stream, latestPdfFile.Name);
await botClient.SendDocumentAsync(chatId, inputOnlineFile);
}
catch (Exception e)
{
Console.WriteLine($"Ошибка при отправке файла: {e.Message}");
await botClient.SendTextMessageAsync(chatId, "Ошибка при отправке файла.");
}
}
else
{
await botClient.SendTextMessageAsync(chatId, "Нет доступных бюллетеней.");
}
}
static FileInfo GetLatestPdfFile(string directoryPath)
{
var directory = new DirectoryInfo(directoryPath);
if (!directory.Exists) return null;
На данный момент сделан bild под windows (скриншот во вложении) и протестирована работа бота(скриншот во вложении).
Код можно улучшать и дорабатывать, я пытался реализовать подачу на вход параметра bot_token через консоль, но столкнулся с проблемой асинхронности, решить пока не удалось.
Во вложении .zip архив проекта без bild под Windows, так как с ним он просто слишком большой и не отправляется.
Для тех кто будет продолжать работать с проектом подмечу, важно использовать .NET 6 и выше, так как библиотека поддерживает работу от 6 версии и очень важно использовать версию библиотеки 18.0.0 , так как при работе с 19 версией будут проблемы с пространством имен Telegram.Bot.Types.ImputFiles в следствии чего будет невозможна работа с ImputOnlineFile.
В процессе разработки собственных приложений на JAVA, используя Winnum.SDK, появилась необходимость создания параметров и групп параметров для управления поведением приложения. У всех стандартных приложений имеются свои параметры и группы параметров, которые доступны по адресу:
Подскажите, пожалуйста, как правильно создавать свои параметры, группы параметров и вложенные группы в группу параметров с использованием хэлперов Winnum.SDK.
Повторяющийся сигнал - это сигнал, который записывается автоматически с определенным интервалом и значением. Такие сигналы могут быть использованы для ввода ручных значений. Рассмотрим вариант использования, в котором стоит задача авторизовать пользователя, привязав его к определенному изделию и снять авторизацию когда это потребуется. Альтернативой этому может быть любой вводимый пользователем сигнал, например текущая операция или причина простоя.
Для работы с повторяемыми сигналами существует системный робот Repeatable Signal Worker. В настройках этого робота можно указать интервал для повторения всех сигналов.
Для контроля и управления всеми повторяющимися сигналами достаточно перейти в Настройки>Администрирование>Повторяющиеся сигналы.
В WINNUM JavaScriptSDK для работы с управляемыми сигналами предусмотрены следующие функции:
Создание нового повторяемого сигнала. Если повторяемый сигнал с заданными параметрами уже есть, то он будет остановлен и использован
Параметр uuid – UUID продукта
Параметр qualifiedPrefix – блокчайн префикс (допускается только кодировка ASCII) - Допустимо оставить пустым
Параметр qualifiedName – квалифицированное имя сигнала (допускается только кодировка ASCII)
counter - тип используемого счетчика, если сигнал используется не для счетчиков указывается "",
baseSdkUtils.service.WNRepeatableSignalHelper.startRepeatableSignal(oid, value, function(data) {})
Запуск созданного заранее повторяемого сигнала
Параметр oid – идентификатор повторяемого сигнала (class name: id), его можно найти в адресной строке при открытии информации о повторяемом сигнале.
Параметр value – повторяемое значение
baseSdkUtils.service.WNRepeatableSignalHelper.stopRepeatableSignal(oid, function(data) {})
Остановка повторяемого сигнала
Параметр oid – идентификатор повторяемого сигнала (class name: id), его можно найти в адресной строке при открытии информации о повторяемом сигнале.
После этого можно проверить, что в списке повторяемых сигналов появилось новое значение.
Важные моменты:
1. Управлять повторяемыми сигналами может только Администратор Устройств или Администратор в Организации.
2. Создавая повторяемый сигнал - не создайте его на месте реального сигнала с тем же именем. В этом случае сигнал будет подмешивать значения в приходящий сигнал.
3. Чтобы увидеть сигнал на устройстве нужно создать Шаблон Объекта Данных, назначить его Шаблону Устройств и Прикрепить этот Шаблон Устройств к Шаблону Изделий (на ваше усмотрение как именно организовать Шаблон Изделия).
Создать и запустить:
Код:
function createSignalOperator(uuid, signal, value){
baseSdkUtils.service.WNRepeatableSignalHelper.createRepeatableSignal(uuid, "", signal ,"",function(data){
for(var i = 0; i < data.getElementsByTagName('item').length ; i++ ) {
var item = data.getElementsByTagName('item')[i];
var oid = baseSdkUtils.decode(item.getAttribute("id"));
Во вложении шаблон, который позволяет назначать станкам операторов в виде повторяющегося сигнала. Для этого получаем список установленных и доступных пользователю приложений - их выведем в виде выпадающего списка, оттуда сразу берем список станков и запускаем вывод значений сигналов в таблицу:
Код:
function getApplication(){
baseSdkUtils.service.WNApplicationHelper.getApplicationInstance('', function (data){
if ( baseSdkUtils.isSuccess(data) ){
var namesApp = [];
var idApp = [];
for (var i = 0; i < data.getElementsByTagName('item').length; i++){
var item = data.getElementsByTagName('item')[i];
var name = baseSdkUtils.decode(item.getAttribute('name'));
namesApp.push(name);
var id = item.getAttribute('id');
idApp.push(id);
}
uniqueNames = new Set(namesApp);
uniqueId = new Set(idApp);
var ApplicationNames = Array.from(uniqueNames);
var ApplicationID = Array.from(uniqueId);
baseSdkUtils.service.WNApplicationHelper.getProduct(_appid_, function(data){
if ( baseSdkUtils.isSuccess(data) ){
for(var i = 0; i < data.getElementsByTagName('item').length ; i++ ) {
var item = data.getElementsByTagName('item')[i];
var product_UUID = item.getAttribute('ProductUUID');
Добрый день, коллеги!
Сегодня мы рассмотрим такую тему как трансформации объектов на странице.
Вращение объекта на странице — без преувеличения одна из самых полезных функций. Этот эффект, будучи простым в понимании, содержит некоторые хитрости, о которых не стоит забывать во время разработки приложения.
Вращение объекта достигается путём использования функции rotate() свойства transform. В отличие от функции перемещения, вращение не может быть описано пикселями, процентами и другими привычными единицами измерения. Для вращения объекта используются специальные «угловые» единицы, среди которых: deg — Градусы. Знакомая многим единица измерения, по которой можно указать, на какой угол повернуть объект относительно его начального положения . grad — Градианы. Отличительной особенностью такой единицы является количество град на один квадрант — 100. То есть полный круг описывается в 400 град. rad — Радианы. Мера, при которой за одну единицу принимается угол дуги, длина которой равна радиусу окружности. turn — Оборот. Количество оборотов окружности, где единица равна одному полному обороту.
Если пользоваться CSS, то функция выглядит следующим образом:
transform: rotate(15deg); // поворот на 15 градусов по часовой стрелке transform: translate(-50%, -50%); // перемещение на 50% вверх и 50% влево
Зная это мы можем написать функцию, которая будет менять угол и положение объекта в зависимости от внешних параметров.
Для дальнейших экспериментов поставим задачу - оживить индикатор от 0 до 100%, в качестве сигнала возьмем загрузку процессора и выведем информацию в виде красивого индикатора, который соберем сами из нескольких элементов.
Наш индикатор будет состоять из трех элементов: фон, стрелка и цифровой индикатор для большей наглядности.
Поскольку стрелка это отдельный элемент, то нам понадобится индикатор без стрелки, либо стрелку нужно удалить самостоятельно
Подойдет абсолютно любой фон в зависимости от величин, которые мы будем демонстрировать. Проценты будут измеряться в диапазоне от 0 до 100, поэтому я выбрал такой же индикатор.
Следующий элемент - стрелочка. Его я нарисовал самостоятельно, если будете выбирать ее в интернете - важно, чтобы у нее не было фона. Еще фон можно удалить самостоятельно. Подбирать размеры и расположение нет необходимости, ось вращения можно корректировать в редакторе.
Когда два элемента подготовлены - можно переходить в редактор динамических приложений.
Создаем контейнер, назначаем ему фон - сам индикатор. Настраиваем, чтобы фон был посередине и не повторялся.
Внутри этого контейнера будет еще два: первый - это стрелочка, ему также настраиваем фон, второй контейнер - цифровая индикация.
Я настроил сигнал на странице при помощи стандартного интерфейса, сигнал назвал "Processor". Теперь я могу вызвать его из любого места страницы.
Начнем с простого отображения сигнала, заодно проверим, что все правильно.
В контейнере, предназначенном для цифровой индикации создаем событие "Проигрывание в цикле".
Код:
//page id:C18FB6C5-962B-4EB6-9F78-716E2EABD2E3
//element id:0DE1C71A-E5AC-4698-AAE5-97AEC1E8E8BA
//LOOP_PLAY
function loop_0DE1C71A_E5A(){
try {
let elem = document.getElementById('0DE1C71A-E5AC-4698-AAE5-97AEC1E8E8BA');
let value = baseSdkUtils.data['Processor'].value;
elem.innerHTML = value;
} catch ( e ) {
}
setTimeout(loop_0DE1C71A_E5A, 1000);
}
loop_0DE1C71A_E5A();
Очень важный момент - у вас element id будет другим, если вы создаете приложение самостоятельно.
Теперь запускаем экземпляр приложения и видим значение сигнала.
Настало время заставить стрелку двигаться вместе со значением.
В поле html вставляем следующий код:
Этот элемент мы создаем для того, чтобы "откалибровать нашу стрелку.
Мы сразу увидим, что на нашей стрелке появилась точка - это и есть ось вращения. Изменяя элементы top и left - переместим точку в нужное нам место.
В данном случае transform: translate(-50%, -50%) - переворачивает систему координат для того чтобы как раз параметры top и left соответствовали оси вращения, которую мы уже будем задавать в событии.
Создаем событие "Проигрывание в цикле":
Код:
//page id:C18FB6C5-962B-4EB6-9F78-716E2EABD2E3
//element id:681DB916-6405-408C-8480-C5AEB30EF032
//LOOP_PLAY
function loop_681DB916_640(){
try {
let elem = document.getElementById('681DB916-6405-408C-8480-C5AEB30EF032');
let value = (baseSdkUtils.data['Processor'].value);
elem.style.transformOrigin = '79% 50%' - эти параметры совпадают с ранее выбранными left И top
Теперь рассмотрим предыдущую строчку:
elem.style.transform = 'rotate(' + (16 + (value * 1.48)) + 'deg)';
Мы используем функцию rotate, в качестве единиц измерения я выбрал градусы, а значит полный круг = 360.
Путем подбора вычисляем, что стрелка показывает на 0 при значении 16 градусов. На 100 она показывает при значении 164 градуса. Таким образом вся шкала составляет 164 - 16 = 148. Делим на 100% и получаем (16 + (value * 1.48)) - теперь стрелка показывает на нужный параметр.
Если вы делаете индикатор с другим фоном или стрелка у вас находится в другом месте (выше или ниже), то числа у вас будут другие.
Шаблон приложения:
export (14).zip (Размер: 362.35 KB / Загрузок: 4)
На последок небольшой бонус:
В скрипт, который мы создали для анимации стрелки добавим еще одну команду:
elem.style.transition = '1s';
И тогда изменение значения сигнала будет сопровождаться плавным переходом к следующему значению.
У свойства transition есть еще много атрибутов, позволяющих делать нелинейные анимации или более сложные.
Спасибо за внимание.
Для того чтобы оформить выгрузку в Excel уже существующей таблицы в динамическом приложении нужно обратиться к ней, перевести в UTF-8 и сформировать ссылку.
1) Возьмём таблицу html, ее можно создать готовую, или сделать самому, назначив id, наполнить ее всеми необходимыми данными, далее будем ее сохранять в xls.
2) сделаем обычную кнопку с надписью скачать и внутри нее создадим событие "нажата кнопка мыши"
Код:
$('#buttonID').click(function(event){
function exportTableToExcel(tableId, filename = 'the_data_you_needed.xls') {
let dataType = 'application/vnd.ms-excel';
let tableSelect = document.getElementById(tableId);
let tableHTML = encodeURIComponent(tableSelect.outerHTML.replace(/ or .*?>/g, '>'));
let link = document.createElement('a');
link.setAttribute('href', 'data:text/csv;charset=utf-8,%EF%BB%BF' + tableHTML);
link.download = filename;
link.click();
}
exportTableToExcel('tableID');
});
Вот и всё!
В этой функции подставляем:
buttonID - ID кнопки, которую нажимаем
tableID - ID таблицы, которую выгружаем
the_data_you_needed.xls - название файла, который получим
Задача: У нас есть стандартный отчет по работе оборудования, он нас устраивает, но при формировании этого отчета необходимо указывать дату и выбирать наш цех. Мы хотим приложение, в котором отчет будет открываться сразу за сегодняшнее число, за наш цех, и показывал всегда актуальную информацию, мы его повесим на телевизор в цеху и трогать не будем.
Решение:
Создаем пустое приложение из шаблона, или просто пустое приложение.
Добавляем пустой контейнер. Для удобства прописываем в поле данные еще один контейнер:
Код:
<div width = '100%' height = '100%' id = fullscreen></div>
Теперь посмотрим на отчет, который мы хотим видеть в приложении:
Посмотрим год непосредственно самого отчета и увидим откуда он берется
Для того чтобы вставить в приложение готовый отчет используется функция baseUtils.injectIntoPage,
ƒ injectIntoPage( url – берем из параметра injectedurl на том элементе, который хотим повторить, я взял отчет по загрузке и меняю в нем параметры, а так же дату , injectId – куда будем вставлять результат , checkAuth – нужно ли быть авторизованным для получения результата (если поставить false, то функция будет работать только там, где не требуется авторизация, в нашем случае используются данные из приложения, а у него есть пользователи) , callBack – далее все как обычно , callBackError)
Теперь рассмотрим атрибут injected_url, в нем написан адрес запроса, его же мы и будем использовать как параметр url.
/Winnum/views/pages/app/agw.jsp?rpc=winnum.views.pages.app.cnc.reports.calendar.TagPriorityWorkReport
&men=streamWorkReportsTable
&appid=winnum.org.app.WNApplicationInstance:8
&oid=winnum.org.domain.WNAdministrativeDomain%3A1
&oid_id=__ref_inputoid&oid_type=text_reference&oid_value2=
&date=03.05.2024
&date_id=__text_date_inputdate
&date_type=text-date
&date_value2=
&formid=report_settings_form
&count=2
&mode=yes Очевидно, что его можно исправить в таких аргументах как date, oid, appid.
oid и appid нас устраивает, осталось только сделать сегодняшний день и запустить отчет в цикле, обновляя его каждые пять минут. Значит создаем событие loop (проигрывание в цикле) и пишем:
Код:
//get today
var today = new Date();
var dd = today.getDate();
if (dd < 10){dd = '0' + dd}
var mm = today.getMonth() + 1;
if (mm < 10){mm = '0' + mm}
var yyyy = today.getFullYear();
var thisday = dd + '.' + mm + '.' + yyyy;
var appid = 'winnum.org.app.WNApplicationInstance:8';
var oid = 'winnum.org.domain.WNAdministrativeDomain%3A1';
var containerId = fullscreen.id;
В данном случае нужно прописать id нашего контейнера, центрального слоя и верхнего меню, для того чтобы все было "резиновым" и на своих местах. Еще в самом начале поменять id контейнера на window.
В результате получится приложение, которое открывает отчет по нагрузке за весь домен и обновляет его каждые 300 секунд, данные берутся из приложения 8. Чтобы получить свой цех - нужно поменять oid на соответствующую ему папку.
Во вложении самообновляемый отчет, который позволяет выбрать участок или весь цех, а так же выбрать приложение для построения графика из выпадающего списка
В процессе разработки динамического приложения появилась необходимость получать все статусы оборудования с учётом приоритетности за текущий день/смену в режиме реального времени. Используя метод getPriorityTagDuration() WINNUM JavaScript SDK получаю данные, которые не соответствуют данным в отчетах.
Например, в приложенном скриншоте №1 с данными полученные функцией getPriorityTagDuration() видны частые появления тега статуса "Станок выключен", хотя в приложенном скриншоте №2 видно, что в отчёте по работе оборудования за текущий день, статуса "Станок выключен" нет. Также не совпадают временные метки старта и окончания действия тегов, например, тег "М0 останов", см. скриншот №3.
В чём может быть причина таких расхождений? Или расчёт длительности тегов, выполняемый в функции getPriorityTagDuration() отличается от методов расчётов для стандартных отчётов? Или требуется какая-то настройка платформы для получения корректных данных?