Если вы хотите, чтобы все подсказки исчезли на всей странице, то удалите их сразу после загрузки страницы. Так вы сразу избавитесь от всплывающих надписей, таких как "Контейнер(Пустой)" и т.д.
В редакторе лучше переименовывать эти атрибуты, они помогают пользоваться поиском на странице в редакторе:
По этой же причине они появятся снова после перезапуска, если их стереть из поля ввода атрибута.
А уже при запуске приложения их можно удалить все одной командой, например так:
Код:
function loadResoursesProcessor(){
document.querySelectorAll('[title]').forEach(element => element.removeAttribute('title'));
// Остальной код
}
Сегодня мы будем делать график загрузки оборудования полностью своими руками:
К этому графику можно добавить временную шкалу и сделать его полноценным отчетом.
У данного метода есть как преимущества, так и недостатки.
Главное преимущество - это точность расположения элементов. Кроме того самостоятельное рисование позволяет создать абсолютно любой график с любыми временными промежутками и отрезками, варьировать размеры и цвета, а так же дает понимание того как этот график строится.
Недостаток в том, что все погрешности измерений, если они предусмотрены - видны на таком графике, т.к. он привязывается ко временным отрезкам, а не относительным величинам, можно увидеть небольшие изменения тегов, в 1 пиксель, из-за чего будут сливаться цвета.
В этой функции мы используем " getPriorityTagDuration " для получения всех временных отрезков тегов исходя из их приоритетности.
В результате мы получаем временные коды в миллисекундах, что дает возможность пересчитать местоположение и ширину закрашенной области. Для подсказки при наведении используем <title> в svg. Кроме того график можно обрезать и растягивать, если нужно разбить его на несколько смен, или сделать подробный график - как в стандартном приложении, для этого не нужно вызывать функцию для каждого часа. она не даст более точный результат, промежутка может быть не видно на графике, только потому, что он меньше пикселя шириной.
Ниже код функции и пример ее вызова:
appid - id приложения, можно увидеть в адресной строке
id - id html элемента, куда вставлять график
from - время от - указывается в закодированном виде, можно использовать baseSdkUtils.encode("20.02.2025 00:00:00");
to - baseSdkUtils.encode("20.02.2025 23:00:00");
product - id продукта, т.е. станка - oid - в карточке станка в адресной строке - "WNProduct"
function createBarCart(appid, id, from, to, product){
var fromYMDformat = from.split("%")[0].split(".")[2] + "-" + from.split("%")[0].split(".")[1] + "-" + from.split("%")[0].split(".")[0];
var toYDMformat = to.split("%")[0].split(".")[2] + "-" + to.split("%")[0].split(".")[1] + "-" + to.split("%")[0].split(".")[0];
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
var dd = yesterday.getDate();
var mm = yesterday.getMonth() + 1;
var yyyy = yesterday.getFullYear();
if(dd<10){ dd = "0" + dd;}
if(mm<10){ mm = "0" + mm;}
var thisday = dd+"."+mm+"."+yyyy;
var today = new Date();
var td=today.getDate();
var tm = today.getMonth() + 1;
var ty = today.getFullYear();
var todaydmy = ty+"-"+tm+"-"+td;
var realtoday = td+"."+tm+"."+ty;
var yesterdaydmy = thisday.split('.')[2] + "-" + thisday.split('.')[1] + "-" + thisday.split('.')[0];
var startOfDayY = new Date(baseSdkUtils.decode(from).split(".")[2].split(" ")[0], baseSdkUtils.decode(from).split(".")[1] - 1, baseSdkUtils.decode(from).split(".")[0], baseSdkUtils.decode(from).split(" ")[1].split(":")[0], baseSdkUtils.decode(from).split(" ")[1].split(":")[1], baseSdkUtils.decode(from).split(" ")[1].split(":")[2]);
var startOfDayMs = Number(startOfDayY.getTime());
var endtOfDayY = new Date(baseSdkUtils.decode(to));
var endOfDayMs = endtOfDayY.getTime();
var arrayForDraw = [];
var tagColorSet = {};
baseSdkUtils.service.WNApplicationHelper.getTag(appid,function(data){
for (var i = 0; i < data.getElementsByTagName('item').length; i++){
var item = data.getElementsByTagName('item')[i];
var tagDescription = baseSdkUtils.decode(item.getAttribute('tagName'));
var tagColor = baseSdkUtils.decode(item.getAttribute('tagColor'));
tagColorSet[tagDescription] = tagColor;
}
baseSdkUtils.service.WNApplicationTagHelper.getPriorityTagDuration(appid, product, fromYMDformat, toYDMformat, function(data){
if ( baseSdkUtils.isSuccess(data) ){
var str = '<svg viewBox="0 0 100 2">';
for (var i = 0; i < data.getElementsByTagName('item').length; i++){
var item = data.getElementsByTagName('item')[i];
var timems = item.getAttribute('timeData');
var tagName = baseSdkUtils.decode(item.getAttribute('TAG'));
var startTime = baseSdkUtils.decode(item.getAttribute('START'));
var endTime = baseSdkUtils.decode(item.getAttribute('END'));
var now = new Date();
var nowMs = now.getTime();
var x1 = (Number(timems.split(',')[0]) - startOfDayMs)/864000;
if(x1 < 0) x1 = 0;
}
var start = baseSdkUtils.decode(item.getAttribute('START'));
var end = baseSdkUtils.decode(item.getAttribute('END'));
var serial = baseSdkUtils.decode(item.getAttribute('SERIAL_NUMBER'));
var duration = baseSdkUtils.decode(item.getAttribute('DURATION'));
Структура <SVG> дает возможность построить векторное изображение, а это значит, что вы можете увеличивать его, приближать или растянуть, качество при этом не упадет.
Вы можете задать свои цвета вместо конструкции ColorTag, назначив их явно, тогда не придется искать их среди возможных тегов этого приложения, но они будут статичны.
График можно обновлять и сделать его онлайн. В самом начале функции определяется множество переменных, это связано с необходимостью получения сегодняшнего и вчерашнего дня, чтобы можно было создавать график за текущую и предшествующую смены. Если вам не нужны привязки к текущим моментам, вы можете пользоваться getPriorityTagDuration сразу, при том время в ней - начала и конца указывать с точностью до суток. Да такой график можно построить за 3 суток сразу. Но помните, что это не длительные отчеты - чем больше данных - тем дольше ждать ответа, а значит между вызовами нужны паузы.
Готовое решение:
1. Скачать файл objectSelector.html в медиабиблиотеку приложения
2. Поместить файл html в контейнер как обычно: $('#id').load(baseSdkUtils.the.media_url + '/ objectSelector.html');
3. Выбранные объекты доступны в элементе: document.getElementById('objectSelectorResult').value
----------------
Теперь подробнее:
objectSelector.html
// Создать векторную шкалу времени в зависимости от смены
// 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');