32 changed files with 1391 additions and 405 deletions
@ -1,73 +0,0 @@ |
|||||
package org.nl.acs.agv; |
|
||||
|
|
||||
import cn.hutool.core.collection.CollUtil; |
|
||||
import cn.hutool.http.HttpRequest; |
|
||||
import cn.hutool.http.HttpResponse; |
|
||||
import com.alibaba.fastjson.JSONObject; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.nl.acs.AcsConfig; |
|
||||
import org.nl.acs.instruction.domain.Instruction; |
|
||||
import org.nl.acs.instruction.service.InstructionService; |
|
||||
import org.nl.config.SpringContextHolder; |
|
||||
import org.nl.system.service.param.ISysParamService; |
|
||||
import org.springframework.stereotype.Component; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 定时查询AGV状态 |
|
||||
*/ |
|
||||
@Slf4j |
|
||||
@Component("queryAGVStatus") |
|
||||
public class QueryAGVStatus { |
|
||||
|
|
||||
public void run() { |
|
||||
InstructionService instructionService = SpringContextHolder.getBean(InstructionService.class); |
|
||||
ISysParamService paramService = SpringContextHolder.getBean(ISysParamService.class); |
|
||||
List<Instruction> allInstFromCache = instructionService.findAllInstFromCache(); |
|
||||
if (CollUtil.isEmpty(allInstFromCache) || allInstFromCache.size() < 1) { |
|
||||
return; |
|
||||
} |
|
||||
for (Instruction instruction : allInstFromCache) { |
|
||||
if ("4".equals(instruction.getInstruction_type())) { |
|
||||
String agvurl = paramService.findByCode(AcsConfig.AGV_URL).getValue(); |
|
||||
JSONObject param = new JSONObject(); |
|
||||
agvurl = agvurl + ":/" + instruction.getInstruction_code(); |
|
||||
log.info("根据运单号查询运单状态的请求:{}", agvurl); |
|
||||
HttpResponse result = HttpRequest.get(agvurl) |
|
||||
.timeout(20000)//超时,毫秒
|
|
||||
.execute(); |
|
||||
log.info("根据运单号查询运单状态的请求反馈:{}", result); |
|
||||
String body = result.body(); |
|
||||
JSONObject json = JSONObject.parseObject(body); |
|
||||
if (result.getStatus() == 200 && json.getString("id").equals(instruction.getInstruction_code())) { |
|
||||
// 已创建=CREATED,
|
|
||||
// 待分配=TOBEDISPATCHED,
|
|
||||
// 正在执行=RUNNING,
|
|
||||
// 完成=FINISHED,
|
|
||||
// 失败=FAILED(主动失败),
|
|
||||
// 终止=STOPPED(被人为终止),
|
|
||||
// 无法执行=Error(参数错误),
|
|
||||
// 等待=WAITING
|
|
||||
//执行中
|
|
||||
String state = json.getString("state"); |
|
||||
if ("RUNNING".equals(state) || "CREATED".equals(state) || "TOBEDISPATCHED".equals(state) || "WAITING".equals(state)) { |
|
||||
instruction.setInstruction_status("1"); |
|
||||
instructionService.update(instruction); |
|
||||
} else if ("FINISHED".equals(state)) { |
|
||||
instruction.setInstruction_status("2"); |
|
||||
try { |
|
||||
instructionService.finish(instruction); |
|
||||
} catch (Exception e) { |
|
||||
log.error("执行完成,但无法更新状态,可能由于参数错误导致的异常"); |
|
||||
e.printStackTrace(); |
|
||||
} |
|
||||
} else if ("STOPPED".equals(state) || "FAILED".equals(state) || "Error".equals(state)) { |
|
||||
instruction.setInstruction_status("1"); |
|
||||
instructionService.update(instruction); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,424 @@ |
|||||
|
package org.nl.acs; /** |
||||
|
* Created by admin on 2019/8/21. |
||||
|
*/ |
||||
|
|
||||
|
import onbon.bx06.Bx6GEnv; |
||||
|
import onbon.bx06.Bx6GScreen; |
||||
|
import onbon.bx06.Bx6GScreenClient; |
||||
|
import onbon.bx06.area.*; |
||||
|
import onbon.bx06.area.page.ImageFileBxPage; |
||||
|
import onbon.bx06.area.page.TextBxPage; |
||||
|
import onbon.bx06.area.page.TextFileBxPage; |
||||
|
import onbon.bx06.cmd.dyn.DynamicBxAreaRule; |
||||
|
import onbon.bx06.file.ProgramBxFile; |
||||
|
import onbon.bx06.message.common.ErrorType; |
||||
|
import onbon.bx06.message.led.ReturnControllerStatus; |
||||
|
import onbon.bx06.message.tcp.ReturnNetwork; |
||||
|
import onbon.bx06.series.Bx6Card; |
||||
|
import onbon.bx06.series.Bx6E; |
||||
|
import onbon.bx06.utils.DisplayStyleFactory.DisplayStyle; |
||||
|
import onbon.bx06.utils.DisplayStyleFactory; |
||||
|
import onbon.bx06.utils.TextBinary; |
||||
|
|
||||
|
import java.awt.*; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @program: bx06_demo |
||||
|
* @description: |
||||
|
* @author: Mr.Feng |
||||
|
* @create: 2019-08-21 14:52 |
||||
|
**/ |
||||
|
public class bx06_demo { |
||||
|
private static String ip = "192.168.81.56"; |
||||
|
private static int port = 5005; |
||||
|
public static void main(String[] args)throws Exception |
||||
|
{ |
||||
|
// 初始化API,此操作只在程序启动时候执行一次即可,多次执行会出现内存错误
|
||||
|
Bx6GEnv.initial(30000); |
||||
|
SendDynamicProgram(); |
||||
|
} |
||||
|
|
||||
|
// 将一个节目发送到控制器
|
||||
|
public static void SendProgram()throws Exception |
||||
|
{ |
||||
|
// 关于显示特技
|
||||
|
// 0:随机显示
|
||||
|
// 1:静止显示
|
||||
|
// 2:快速打出
|
||||
|
// 3:向左移动
|
||||
|
// 4:向左连移
|
||||
|
// 5:向上移动
|
||||
|
// 6:向上连移
|
||||
|
// 7:闪烁
|
||||
|
// 8:飘雪
|
||||
|
// 9:冒泡
|
||||
|
// 10:中间移出
|
||||
|
// 11:左右移入
|
||||
|
// 12:左右交叉移入
|
||||
|
// 13:上下交叉移入
|
||||
|
// 14:花卷闭合
|
||||
|
// 15:花卷打开
|
||||
|
// 16:向左拉伸
|
||||
|
// 17:向右拉伸
|
||||
|
// 18:向上拉伸
|
||||
|
// 19:向下拉伸
|
||||
|
// 20:向左镭射
|
||||
|
// 21:向右镭射
|
||||
|
// 22:向上镭射
|
||||
|
// 23:向下镭射
|
||||
|
// 24:左右交叉拉幕
|
||||
|
// 25:上下交叉拉幕
|
||||
|
// 26:分散左拉
|
||||
|
// 27:水平百叶
|
||||
|
// 28:垂直百叶
|
||||
|
// 29:向左拉幕
|
||||
|
// 30:向右拉幕
|
||||
|
// 31:向上拉幕
|
||||
|
// 32:向下拉幕
|
||||
|
// 33:左右闭合
|
||||
|
// 34:左右对开
|
||||
|
// 35:上下闭合
|
||||
|
// 36:上下对开
|
||||
|
// 37;向右移动
|
||||
|
// 38:向右连移
|
||||
|
// 39:向下移动
|
||||
|
// 40:向下连移
|
||||
|
// 41:45度左旋
|
||||
|
// 42:180度左旋
|
||||
|
// 43:90度右旋
|
||||
|
// 44:45度右旋
|
||||
|
// 45:180度右旋
|
||||
|
// 46:90度右旋
|
||||
|
// 47:菱形打开
|
||||
|
// 48:菱形闭合
|
||||
|
DisplayStyle[] styles = DisplayStyleFactory.getStyles().toArray(new DisplayStyle[0]); |
||||
|
|
||||
|
// 创建screen对象,用于与控制卡的交互
|
||||
|
// 第二个参数是控制卡型号,只有型号对才能正常通讯,否则会出现逾时未回应,如果使用的型号API中未定义,用new Bx6M()替代
|
||||
|
Bx6GScreenClient screen = new Bx6GScreenClient( "MyScreen",new Bx6E() ); |
||||
|
|
||||
|
// 连接控制器
|
||||
|
screen.connect( ip,port); |
||||
|
|
||||
|
// 创建节目 一个节目相当于一屏显示内容
|
||||
|
ProgramBxFile pf = new ProgramBxFile( "P000",screen.getProfile() ); |
||||
|
|
||||
|
// 创建一个分区
|
||||
|
// 分别输入X,Y,width,heigth
|
||||
|
// 注意区域坐标和宽度高度不要越界
|
||||
|
TextCaptionBxArea area = new TextCaptionBxArea( 0,0,160,64,screen.getProfile() ); |
||||
|
|
||||
|
// 创建一个数据页
|
||||
|
// 第一行数据
|
||||
|
TextBxPage page = new TextBxPage("仰邦科技欢迎你!"); |
||||
|
// 第二行数据
|
||||
|
page.newLine( "这是第二行数据" ); |
||||
|
// 设置字体
|
||||
|
page.setFont( new Font("宋体", Font.PLAIN,12) ); |
||||
|
// 设置显示特技为快速打出
|
||||
|
page.setDisplayStyle( styles[2] ); |
||||
|
|
||||
|
// 数据页可以是图片
|
||||
|
ImageFileBxPage iPage = new ImageFileBxPage( "D:a/004.bmp" ); |
||||
|
|
||||
|
// 数据页可以是txt文件
|
||||
|
TextFileBxPage tPage = new TextFileBxPage( "D:a/001.txt" ); |
||||
|
|
||||
|
// 将前面的page添加到area中,page不可以是表格,如果需要Led显示表格,请先将表格绘制成图片
|
||||
|
area.addPage( page ); |
||||
|
area.addPage( iPage ); |
||||
|
area.addPage( tPage ); |
||||
|
|
||||
|
// 将area添加到节目中,节目中可以添加多个area
|
||||
|
pf.addArea( area ); |
||||
|
|
||||
|
// 更新节目
|
||||
|
screen.writeProgram( pf ); |
||||
|
|
||||
|
// 断开连接
|
||||
|
screen.disconnect(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// 将多个节目发送到控制器并显示
|
||||
|
public static void SendPrograms()throws Exception |
||||
|
{ |
||||
|
DisplayStyle[] styles = DisplayStyleFactory.getStyles().toArray(new DisplayStyle[0]); |
||||
|
|
||||
|
Bx6GScreenClient screen = new Bx6GScreenClient( "MyScreen",new Bx6E() ); |
||||
|
|
||||
|
screen.connect( ip,port ); |
||||
|
|
||||
|
ProgramBxFile pf = new ProgramBxFile( "P000",screen.getProfile() ); |
||||
|
|
||||
|
// 创建一个时间区
|
||||
|
DateTimeBxArea dtArea = new DateTimeBxArea( 0,0,160,64,screen.getProfile() ); |
||||
|
|
||||
|
// 设定时间区多行显示
|
||||
|
dtArea.setMultiline( true ); |
||||
|
|
||||
|
// 设定日期显示格式 NULL表示不显示日期
|
||||
|
dtArea.setDateStyle( DateStyle.YYYY_MM_DD_1 ); |
||||
|
// 设定时间显示格式 NULL表示不显示时间
|
||||
|
dtArea.setTimeStyle( TimeStyle.HH12_MM_SS_1 ); |
||||
|
// 设定星期显示格式 NULL表示不显示星期
|
||||
|
dtArea.setWeekStyle( WeekStyle.CHINESE ); |
||||
|
// 设定时间区字体
|
||||
|
dtArea.setFont( new Font("宋体",Font.PLAIN,12) ); |
||||
|
|
||||
|
pf.addArea( dtArea ); |
||||
|
|
||||
|
// 创建第二个节目
|
||||
|
ProgramBxFile pf_2 = new ProgramBxFile( "P001",screen.getProfile() ); |
||||
|
TextCaptionBxArea area = new TextCaptionBxArea( 0,0,160,64,screen.getProfile() ); |
||||
|
TextBxPage page = new TextBxPage( "Led控制系统首选仰邦" ); |
||||
|
page.setDisplayStyle( styles[4] ); |
||||
|
area.addPage( page ); |
||||
|
pf_2.addArea( area ); |
||||
|
|
||||
|
// 创建一个list
|
||||
|
ArrayList<ProgramBxFile> plist = new ArrayList<ProgramBxFile>( ); |
||||
|
plist.add( pf ); |
||||
|
plist.add( pf_2 ); |
||||
|
|
||||
|
screen.writePrograms( plist ); |
||||
|
|
||||
|
// 如果需要,可以从控制器回读控制器上已有的节目列表
|
||||
|
List<String> pfs = screen.readProgramList(); |
||||
|
for(String program:pfs) |
||||
|
{ |
||||
|
System.out.println( program ); |
||||
|
} |
||||
|
|
||||
|
screen.disconnect(); |
||||
|
} |
||||
|
|
||||
|
// 更新动态区
|
||||
|
// 六代卡中,只有BX-6E系列、BX-6EX系列和BX-6Q系列支持动态区
|
||||
|
// 动态区是完全独立于节目,其显示内容可以按区域单独更新
|
||||
|
// 动态区可以与节目一起播放,也可以单独播放
|
||||
|
// 动态区显示内容存储于ARM,掉电不保存,没有刷新次数限制
|
||||
|
|
||||
|
// 动态区单独播放
|
||||
|
public static void SendDynamic()throws Exception |
||||
|
{ |
||||
|
DisplayStyle[] styles = DisplayStyleFactory.getStyles().toArray(new DisplayStyle[0]); |
||||
|
|
||||
|
Bx6GScreenClient screen = new Bx6GScreenClient( "MyScreen",new Bx6E() ); |
||||
|
|
||||
|
screen.connect( ip,port ); |
||||
|
|
||||
|
// 创建动态区
|
||||
|
// BX-6E BX-6EX系列支持4个动态区,BX-6Q系列支持32个动态区
|
||||
|
DynamicBxAreaRule rule = new DynamicBxAreaRule(); |
||||
|
// 设定动态区ID ,此处ID为0 ,多个动态区ID不能相同
|
||||
|
rule.setId(0); |
||||
|
// 设定异步节目停止播放,仅播放动态区
|
||||
|
// 0:与异步节目一起播放
|
||||
|
// 1:异步节目 停止播放,仅播放动态区
|
||||
|
// 2:当播放完节目编号坐高的异步节目后播放该动态区
|
||||
|
rule.setImmediatePlay( (byte)1 ); |
||||
|
// 设定动态区循环播放
|
||||
|
// 0:循环显示
|
||||
|
// 1:显示完成后静止显示最后一页数据
|
||||
|
// 2:循环显示,超过设定时间后数据仍未更新时不再显示
|
||||
|
// 3:循环显示,超过设定时间后数据仍未更新时显示Logo信息
|
||||
|
// 4:循环显示,显示完成最后一页后就不再显示
|
||||
|
rule.setRunMode( (byte)0 ); |
||||
|
|
||||
|
DynamicBxArea area = new DynamicBxArea( 0,0,160,32,screen.getProfile() ); |
||||
|
|
||||
|
TextBxPage page = new TextBxPage( "第一个动态区" ); |
||||
|
|
||||
|
page.setFont( new Font( "宋体",Font.PLAIN,12 ) ); |
||||
|
|
||||
|
page.setDisplayStyle( styles[2] ); |
||||
|
|
||||
|
area.addPage( page ); |
||||
|
|
||||
|
screen.writeDynamic( rule,area ); |
||||
|
|
||||
|
// 创建第二个动态区
|
||||
|
DynamicBxAreaRule rule_2 = new DynamicBxAreaRule(); |
||||
|
rule_2.setId( 1 ); |
||||
|
rule_2.setImmediatePlay( (byte)1 ); |
||||
|
rule_2.setRunMode( (byte)0 ); |
||||
|
DynamicBxArea area_2 = new DynamicBxArea( 0,32,160,32,screen.getProfile() ); |
||||
|
TextBxPage page_2 = new TextBxPage( "第二个动态区" ); |
||||
|
page_2.setFont( new Font("宋体",Font.PLAIN,12) ); |
||||
|
page_2.setDisplayStyle( styles[2] ); |
||||
|
area_2.addPage( page_2 ); |
||||
|
screen.writeDynamic( rule_2,area_2 ); |
||||
|
|
||||
|
screen.disconnect(); |
||||
|
} |
||||
|
|
||||
|
// 动态区和节目一起播放
|
||||
|
public static void SendDynamicProgram()throws Exception |
||||
|
{ |
||||
|
DisplayStyle[] styles = DisplayStyleFactory.getStyles().toArray(new DisplayStyle[0]); |
||||
|
|
||||
|
Bx6GScreenClient screen = new Bx6GScreenClient( "MyScreen",new Bx6E() ); |
||||
|
|
||||
|
screen.connect( ip,port ); |
||||
|
|
||||
|
ProgramBxFile pf = new ProgramBxFile( 0,screen.getProfile() ); |
||||
|
|
||||
|
TextCaptionBxArea area = new TextCaptionBxArea( 0,0,160,32,screen.getProfile() ); |
||||
|
|
||||
|
TextBxPage page = new TextBxPage( "这是节目" ); |
||||
|
|
||||
|
area.addPage( page ); |
||||
|
|
||||
|
pf.addArea( area ); |
||||
|
|
||||
|
screen.writeProgram( pf ); |
||||
|
|
||||
|
DynamicBxAreaRule rule = new DynamicBxAreaRule(); |
||||
|
rule.setId(0); |
||||
|
rule.setRunMode( (byte)0 ); |
||||
|
// 新增动态区关联异步节目
|
||||
|
// 一旦关联了某个异步节目,则该节目和动态区一起播放
|
||||
|
// 设置动态区和节目关联
|
||||
|
// 设定是否关联全部节目
|
||||
|
// true: 所有异步节目播放是都允许播放该动态区
|
||||
|
// false:由规则来决定
|
||||
|
rule.setRelativeAllPrograms( false ); |
||||
|
rule.addRelativeProgram( 0 ); |
||||
|
|
||||
|
DynamicBxArea dArea = new DynamicBxArea( 0,32,160,32,screen.getProfile() ); |
||||
|
|
||||
|
TextBxPage dPage = new TextBxPage( "这是动态区" ); |
||||
|
dPage.setDisplayStyle( styles[2] ); |
||||
|
dPage.setFont( new Font( "宋体",Font.PLAIN,12 ) ); |
||||
|
|
||||
|
dArea.addPage( dPage ); |
||||
|
|
||||
|
screen.writeDynamic( rule,dArea ); |
||||
|
|
||||
|
List<String> pfs = screen.readProgramList(); |
||||
|
for(String program : pfs) |
||||
|
{ |
||||
|
System.out.println( program ); |
||||
|
} |
||||
|
|
||||
|
screen.disconnect(); |
||||
|
} |
||||
|
|
||||
|
// 关于语音播报区域
|
||||
|
// 语音播放目前只有六代部分卡支持
|
||||
|
public static void SendSound()throws Exception |
||||
|
{ |
||||
|
Bx6GScreenClient screen = new Bx6GScreenClient( "MyScreen",new Bx6E() ); |
||||
|
|
||||
|
screen.connect( ip,port ); |
||||
|
|
||||
|
DisplayStyle[] styles = DisplayStyleFactory.getStyles().toArray(new DisplayStyle[0]); |
||||
|
|
||||
|
ProgramBxFile pf = new ProgramBxFile( "P000",screen.getProfile() ); |
||||
|
|
||||
|
// 语音部分
|
||||
|
TextCaptionBxArea area_sound = new TextCaptionBxArea( 0,0,160,16,screen.getProfile()); |
||||
|
area_sound.setVoiceContent( "黑A12345请到淀粉副产品库DF-01月台" );// 该字符串会被语音播报
|
||||
|
area_sound.setVoiceFlag( true ); |
||||
|
area_sound.setVoiceReplayTimes( 2 );// 设置重复播报3次,如果不设置,默认一直播报
|
||||
|
// 语音的其他设置都在area_sound中设置
|
||||
|
|
||||
|
// 显示部分_1
|
||||
|
TextCaptionBxArea area_display_1 = new TextCaptionBxArea( 0,0,160,48,screen.getProfile() ); |
||||
|
TextBxPage page_display_1 = new TextBxPage( "黑A12345" ); |
||||
|
page_display_1.setFont( new Font( "宋体",Font.PLAIN,30 ) ); |
||||
|
page_display_1.setVerticalAlignment( TextBinary.Alignment.CENTER );// 设置水平居中
|
||||
|
page_display_1.setHorizontalAlignment( TextBinary.Alignment.CENTER );// 设置垂直居中
|
||||
|
page_display_1.setDisplayStyle( styles[2] ); |
||||
|
area_display_1.addPage( page_display_1 ); |
||||
|
|
||||
|
// 显示部分_2
|
||||
|
TextCaptionBxArea area_display_2 = new TextCaptionBxArea( 0,48,160,48,screen.getProfile() ); |
||||
|
TextBxPage page_diaplay_2 = new TextBxPage( "请到淀粉副产品库" ); |
||||
|
page_diaplay_2.newLine( "DF-01月台" ); |
||||
|
page_diaplay_2.setFont( new Font( "宋体",Font.PLAIN,16 ) ); |
||||
|
page_diaplay_2.setVerticalAlignment( TextBinary.Alignment.CENTER ); |
||||
|
page_diaplay_2.setHorizontalAlignment( TextBinary.Alignment.CENTER ); |
||||
|
page_diaplay_2.setDisplayStyle( styles[2] ); |
||||
|
area_display_2.addPage( page_diaplay_2 ); |
||||
|
|
||||
|
pf.addArea( area_sound ); |
||||
|
pf.addArea( area_display_1 ); |
||||
|
pf.addArea( area_display_2 ); |
||||
|
|
||||
|
screen.writeProgram( pf ); |
||||
|
|
||||
|
screen.disconnect(); |
||||
|
} |
||||
|
|
||||
|
// 其他一些常用命令
|
||||
|
public static void SendCmd()throws Exception |
||||
|
{ |
||||
|
Bx6GScreenClient screen = new Bx6GScreenClient( "MyScreen",new Bx6E() ); |
||||
|
screen.connect( ip,port ); |
||||
|
// 关机命令
|
||||
|
screen.turnOff(); |
||||
|
// 开机命令
|
||||
|
screen.turnOn(); |
||||
|
// ping命令
|
||||
|
screen.ping(); |
||||
|
// 查询控制器状态
|
||||
|
screen.checkControllerStatus(); |
||||
|
// 查询控制器内存
|
||||
|
screen.checkMemVolumes(); |
||||
|
// 校时命令
|
||||
|
screen.syncTime(); |
||||
|
// 锁定屏幕当前画面
|
||||
|
screen.lock(); |
||||
|
// 解除锁定屏幕当前画面
|
||||
|
screen.unlock(); |
||||
|
// 通过以下接口回读控制器状态
|
||||
|
Bx6GScreen.Result<ReturnControllerStatus> result = screen.checkControllerStatus(); |
||||
|
if(result.isOK()) |
||||
|
{ |
||||
|
ReturnControllerStatus status = result.reply; |
||||
|
status.getBrightness(); // 取得亮度值
|
||||
|
status.getTemperature1(); // 取得温度传感器温度值
|
||||
|
// status 还有很多接口,根据实际应用进行调用
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ErrorType error = result.getError(); |
||||
|
System.out.println( error ); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// WindSpeed 字节数 2 风速(除以10为当前值) 0xffff时无效
|
||||
|
// WindDirction 字节数 2 风向(当前值) 0xffff时无效
|
||||
|
// PM2.5 字节数 2 PM2.5值(当前值) 0xffff时无效
|
||||
|
// PM10 字节数 2 PM10值(当前值) 0xffff时无效
|
||||
|
Bx6GScreenClient.Result<ReturnNetwork> result1 = screen.searchNetwork(); |
||||
|
byte[] temp = result1.reply.getReserved1(); // 返回的前8个字节为上面注释里的定义
|
||||
|
if(temp[1]*256+temp[0]!=0xffff) |
||||
|
{ |
||||
|
System.out.println("风速:"+(temp[1]*256+temp[0])/10); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
System.out.println("无数据"); |
||||
|
} |
||||
|
if (temp[3]*256+temp[2]!=0xffff) |
||||
|
{ |
||||
|
System.out.println("风向:"+(temp[3]*256+temp[2])); //0:0°北风 1:45°东北风 2:90°东风 3:135°东南风 4:180°南风 5:225°西南风 6:270°西风 7:315°西北风
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
System.out.println("无数据"); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
System.out.println("保留字节:"+temp); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
package org.nl.acs.ext.socket; |
||||
|
|
||||
|
import io.netty.bootstrap.AbstractBootstrap; |
||||
|
import io.netty.channel.Channel; |
||||
|
|
||||
|
import java.net.SocketAddress; |
||||
|
|
||||
|
/* |
||||
|
* @author ZZQ |
||||
|
* @Date 2024/1/22 10:01 |
||||
|
*/ |
||||
|
public abstract class AbstraceServer { |
||||
|
|
||||
|
|
||||
|
public AbstraceServer(SocketAddress address) { |
||||
|
this.address = address; |
||||
|
if (channel!=null){ |
||||
|
doDestroy(); |
||||
|
} |
||||
|
doOpen(); |
||||
|
doConnect(); |
||||
|
} |
||||
|
|
||||
|
public AbstractBootstrap server; |
||||
|
public SocketAddress address; |
||||
|
public Channel channel; |
||||
|
|
||||
|
public abstract void doOpen(); |
||||
|
|
||||
|
public void doDestroy(){ |
||||
|
if (channel!=null){ |
||||
|
channel.close(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public abstract void doConnect() ; |
||||
|
|
||||
|
public void doDisConnect(){ |
||||
|
if (channel!=null){ |
||||
|
channel.close(); |
||||
|
doConnect(); |
||||
|
} |
||||
|
}; |
||||
|
} |
@ -0,0 +1,82 @@ |
|||||
|
package org.nl.acs.ext.socket; |
||||
|
|
||||
|
import io.netty.bootstrap.ServerBootstrap; |
||||
|
import io.netty.channel.*; |
||||
|
import io.netty.channel.nio.NioEventLoopGroup; |
||||
|
import io.netty.channel.socket.SocketChannel; |
||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel; |
||||
|
import io.netty.handler.codec.string.StringDecoder; |
||||
|
import io.netty.handler.codec.string.StringEncoder; |
||||
|
import io.netty.handler.timeout.IdleStateHandler; |
||||
|
import io.netty.util.concurrent.Future; |
||||
|
|
||||
|
import java.net.SocketAddress; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS; |
||||
|
|
||||
|
/* |
||||
|
* @author ZZQ |
||||
|
* @Date 2024/7/5 14:53 |
||||
|
*/ |
||||
|
public class HeartServer extends AbstraceServer { |
||||
|
|
||||
|
private static EventLoopGroup boss = new NioEventLoopGroup(); |
||||
|
private static EventLoopGroup worker = new NioEventLoopGroup(); |
||||
|
|
||||
|
public HeartServer(SocketAddress address) { |
||||
|
super(address); |
||||
|
} |
||||
|
|
||||
|
// 非阻塞IO线程组
|
||||
|
@Override |
||||
|
public void doOpen() { |
||||
|
ServerBootstrap bootstrap = new ServerBootstrap(); |
||||
|
AbstraceServer body = this; |
||||
|
bootstrap |
||||
|
.group(boss, worker) |
||||
|
.channel(NioServerSocketChannel.class) |
||||
|
// .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
|
||||
|
.childHandler(new ChannelInitializer<SocketChannel>() { |
||||
|
@Override |
||||
|
protected void initChannel(SocketChannel ch) throws Exception { |
||||
|
ch.pipeline() |
||||
|
.addLast("client-idle-handler", new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS)) |
||||
|
.addLast( new StringEncoder()) |
||||
|
.addLast( new StringDecoder()) |
||||
|
.addLast(new HeartServerHandler(body)); |
||||
|
} |
||||
|
}); |
||||
|
server = bootstrap; |
||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> this.doDestroy())); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void doDestroy(){ |
||||
|
System.out.println("--------服务停机--------"); |
||||
|
if (channel != null) { |
||||
|
channel.close(); |
||||
|
} |
||||
|
Future<?> bossGroupShutdownFuture = boss.shutdownGracefully(); |
||||
|
Future<?> workerGroupShutdownFuture = worker.shutdownGracefully(); |
||||
|
bossGroupShutdownFuture.syncUninterruptibly(); |
||||
|
workerGroupShutdownFuture.syncUninterruptibly(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void doConnect() { |
||||
|
ChannelFuture future = server.bind(address); |
||||
|
boolean ret = future.awaitUninterruptibly(3000, MILLISECONDS); |
||||
|
if (ret && future.isSuccess()) { |
||||
|
Channel newChannel = future.channel(); |
||||
|
if (channel != null) { |
||||
|
channel.close(); |
||||
|
channel = newChannel; |
||||
|
} |
||||
|
} else if (future.cause() != null) { |
||||
|
Throwable cause = future.cause(); |
||||
|
cause.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,56 @@ |
|||||
|
package org.nl.acs.ext.socket; |
||||
|
|
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.channel.SimpleChannelInboundHandler; |
||||
|
import io.netty.handler.timeout.IdleState; |
||||
|
import io.netty.handler.timeout.IdleStateEvent; |
||||
|
|
||||
|
/* |
||||
|
* @author ZZQ |
||||
|
* @Date 2024/1/22 10:24 |
||||
|
*/ |
||||
|
public class HeartServerHandler extends SimpleChannelInboundHandler { |
||||
|
private AbstraceServer server; |
||||
|
|
||||
|
public HeartServerHandler(AbstraceServer server) { |
||||
|
this.server = server; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
||||
|
Online.isOnline = false; |
||||
|
System.out.println("服务端断开连接-----"); |
||||
|
ctx.close(); |
||||
|
super.channelInactive(ctx); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception { |
||||
|
Online.isOnline = true; |
||||
|
System.out.println("服务端收到连接-----连接"); |
||||
|
super.channelActive(ctx); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { |
||||
|
if (!Online.isOnline) { |
||||
|
Online.isOnline = true; |
||||
|
} |
||||
|
System.out.println("接收到消息" + msg.toString()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { |
||||
|
if (evt instanceof IdleStateEvent) { |
||||
|
IdleStateEvent stateEvent = (IdleStateEvent) evt; |
||||
|
System.out.println(stateEvent.state()); |
||||
|
if (stateEvent.state() == IdleState.READER_IDLE) { |
||||
|
Online.isOnline = false; |
||||
|
System.out.println("--------手动关闭连接--------"); |
||||
|
ctx.close(); |
||||
|
} |
||||
|
} |
||||
|
super.userEventTriggered(ctx, evt); |
||||
|
} |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
package org.nl.acs.ext.socket; |
||||
|
|
||||
|
/** |
||||
|
* @Description TODO |
||||
|
* @Author Gengby |
||||
|
* @Date 2024/7/9 |
||||
|
*/ |
||||
|
public class Online { |
||||
|
public static boolean isOnline = false; |
||||
|
} |
@ -0,0 +1,61 @@ |
|||||
|
package org.nl.system.service.quartz.task; |
||||
|
|
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.lucene.document.Document; |
||||
|
import org.apache.lucene.index.*; |
||||
|
import org.apache.lucene.search.*; |
||||
|
import org.apache.lucene.store.FSDirectory; |
||||
|
import org.apache.lucene.util.BytesRef; |
||||
|
import org.nl.config.lucene.config.LuceneAppender; |
||||
|
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; |
||||
|
import org.springframework.core.io.ClassPathResource; |
||||
|
import org.springframework.core.io.Resource; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.nio.file.Paths; |
||||
|
import java.time.Instant; |
||||
|
import java.time.temporal.ChronoUnit; |
||||
|
import java.util.Date; |
||||
|
import java.util.Properties; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @author onepiece |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class AutoCleanLuceneLog { |
||||
|
|
||||
|
private static final long MAX_FILE_AGE_DAYS = 10; |
||||
|
|
||||
|
public void run() { |
||||
|
try { |
||||
|
Resource resource = new ClassPathResource("config/application.yml"); |
||||
|
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean(); |
||||
|
yamlPropertiesFactoryBean.setResources(resource); |
||||
|
Properties properties = yamlPropertiesFactoryBean.getObject(); |
||||
|
String logDirectory = properties.getProperty("lucene.index.path"); |
||||
|
|
||||
|
Instant now = Instant.now(); |
||||
|
Instant cutoffTime = now.minus(MAX_FILE_AGE_DAYS, ChronoUnit.DAYS); |
||||
|
String cutoffTimeStr = DateUtil.format(Date.from(cutoffTime), "yyyy-MM-dd HH:mm:ss.SSS"); |
||||
|
|
||||
|
FSDirectory dir = FSDirectory.open(Paths.get(logDirectory)); |
||||
|
IndexWriter writer = LuceneAppender.indexWriter; |
||||
|
|
||||
|
try (IndexReader reader = DirectoryReader.open(dir)) { |
||||
|
IndexSearcher searcher = new IndexSearcher(reader); |
||||
|
Query query = new TermRangeQuery("logTime", new BytesRef(""), new BytesRef(cutoffTimeStr), true, true); |
||||
|
TopDocs topDocs = searcher.search(query, Integer.MAX_VALUE); |
||||
|
for (ScoreDoc scoreDoc : topDocs.scoreDocs) { |
||||
|
Document doc = searcher.doc(scoreDoc.doc); |
||||
|
writer.deleteDocuments(new Term("logTime", doc.get("logTime"))); |
||||
|
} |
||||
|
} |
||||
|
writer.commit(); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,159 +0,0 @@ |
|||||
package org.nl.system.service.quartz.task; |
|
||||
|
|
||||
import cn.hutool.core.collection.CollectionUtil; |
|
||||
import cn.hutool.core.date.DateUtil; |
|
||||
import cn.hutool.core.util.IdUtil; |
|
||||
import cn.hutool.core.util.StrUtil; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.nl.acs.device.device.domain.Device; |
|
||||
import org.nl.acs.device.device.service.DeviceAppService; |
|
||||
import org.nl.acs.device.driver.storage.standard_storage.StandardStorageDeviceDriver; |
|
||||
import org.nl.acs.instruction.domain.Instruction; |
|
||||
import org.nl.acs.instruction.enums.InstructionStatusEnum; |
|
||||
import org.nl.acs.instruction.service.InstructionService; |
|
||||
import org.nl.acs.route.service.RouteLineService; |
|
||||
import org.nl.acs.route.service.dto.RouteLineDto; |
|
||||
import org.nl.acs.task.enums.TaskStatusEnum; |
|
||||
import org.nl.acs.task.service.TaskService; |
|
||||
import org.nl.acs.task.service.dto.TaskDto; |
|
||||
import org.nl.common.utils.SecurityUtils; |
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||
import org.springframework.stereotype.Component; |
|
||||
|
|
||||
import java.util.Arrays; |
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 自动创建堆垛机出库指令 |
|
||||
*/ |
|
||||
@Slf4j |
|
||||
@Component |
|
||||
public class AutoCreateDDJInst { |
|
||||
|
|
||||
@Autowired |
|
||||
private TaskService taskService; |
|
||||
@Autowired |
|
||||
private InstructionService instructionService; |
|
||||
@Autowired |
|
||||
private RouteLineService routeLineService; |
|
||||
@Autowired |
|
||||
private DeviceAppService deviceAppService; |
|
||||
|
|
||||
|
|
||||
public void run() throws Exception { |
|
||||
List<TaskDto> list = taskService.queryAllByStatus("0"); |
|
||||
for (int i = 0; i < list.size(); i++) { |
|
||||
TaskDto acsTask = list.get(i); |
|
||||
String start_device_code = acsTask.getStart_device_code(); |
|
||||
Device startDevice = deviceAppService.findDeviceByCode(start_device_code); |
|
||||
if (startDevice != null && startDevice.getDeviceDriver() instanceof StandardStorageDeviceDriver) { |
|
||||
String taskid = acsTask.getTask_id(); |
|
||||
String taskcode = acsTask.getTask_code(); |
|
||||
String task_type = acsTask.getTask_type(); |
|
||||
String vehiclecode = acsTask.getVehicle_code(); |
|
||||
String storage_task_type = acsTask.getStorage_task_type(); |
|
||||
String priority = acsTask.getPriority(); |
|
||||
String is_send = acsTask.getIs_send(); |
|
||||
|
|
||||
String start_point_code = acsTask.getStart_point_code(); |
|
||||
|
|
||||
String put_device_code = acsTask.getPut_device_code(); |
|
||||
String put_point_code = acsTask.getPut_point_code(); |
|
||||
|
|
||||
String next_device_code = acsTask.getNext_device_code(); |
|
||||
String next_point_code = acsTask.getNext_point_code(); |
|
||||
|
|
||||
String start_point_code2 = acsTask.getStart_point_code2(); |
|
||||
String start_device_code2 = acsTask.getStart_device_code2(); |
|
||||
|
|
||||
String next_point_code2 = acsTask.getNext_point_code2(); |
|
||||
String next_device_code2 = acsTask.getNext_device_code2(); |
|
||||
|
|
||||
String route_plan_code = acsTask.getRoute_plan_code(); |
|
||||
String vehicleType = acsTask.getVehicle_type(); |
|
||||
String agv_system_type = acsTask.getAgv_system_type(); |
|
||||
|
|
||||
String start_height = acsTask.getStart_height(); |
|
||||
String next_height = acsTask.getNext_height(); |
|
||||
|
|
||||
|
|
||||
if (StrUtil.equals(is_send, "0")) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
List<RouteLineDto> shortPathsList = routeLineService.getShortPathLines(acsTask.getStart_device_code(), acsTask.getNext_device_code(), route_plan_code); |
|
||||
if (CollectionUtil.isEmpty(shortPathsList)) { |
|
||||
shortPathsList = routeLineService.getShortPathLinesByCode(acsTask.getStart_device_code(), route_plan_code); |
|
||||
} |
|
||||
if (CollectionUtil.isEmpty(shortPathsList)) { |
|
||||
acsTask.setRemark("路由不通"); |
|
||||
taskService.updateByCodeFromCache(acsTask); |
|
||||
continue; |
|
||||
} |
|
||||
RouteLineDto routeLineDto = shortPathsList.get(0); |
|
||||
String path = routeLineDto.getPath(); |
|
||||
String[] str = path.split("->"); |
|
||||
List<String> pathlist = Arrays.asList(str); |
|
||||
int index = 0; |
|
||||
for (int m = 0; m < pathlist.size(); m++) { |
|
||||
if (pathlist.get(m).equals(start_device_code)) { |
|
||||
index = m + 1; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
next_device_code = pathlist.get(index); |
|
||||
|
|
||||
if (StrUtil.equals(deviceAppService.findDeviceTypeByCode(next_device_code), "storage")) { |
|
||||
next_point_code = next_device_code + "-" + acsTask.getTo_y() + "-" + acsTask.getTo_z(); |
|
||||
} else { |
|
||||
next_point_code = next_device_code; |
|
||||
} |
|
||||
|
|
||||
Instruction instdto = new Instruction(); |
|
||||
instdto.setInstruction_type(task_type); |
|
||||
instdto.setInstruction_id(IdUtil.simpleUUID()); |
|
||||
instdto.setRoute_plan_code(route_plan_code); |
|
||||
instdto.setRemark(acsTask.getRemark()); |
|
||||
instdto.setMaterial(acsTask.getMaterial()); |
|
||||
instdto.setQuantity(acsTask.getQuantity()); |
|
||||
instdto.setTask_id(taskid); |
|
||||
instdto.setTask_code(taskcode); |
|
||||
instdto.setVehicle_code(vehiclecode); |
|
||||
String now = DateUtil.now(); |
|
||||
instdto.setCreate_time(now); |
|
||||
instdto.setCreate_by(SecurityUtils.getCurrentNickName()); |
|
||||
|
|
||||
instdto.setStart_device_code(start_device_code); |
|
||||
instdto.setStart_point_code(start_point_code); |
|
||||
instdto.setPut_device_code(put_device_code); |
|
||||
instdto.setPut_point_code(put_point_code); |
|
||||
instdto.setNext_device_code(next_device_code); |
|
||||
instdto.setNext_point_code(next_point_code); |
|
||||
|
|
||||
instdto.setStart_point_code2(start_point_code2); |
|
||||
instdto.setStart_device_code2(start_device_code2); |
|
||||
instdto.setNext_point_code2(next_point_code2); |
|
||||
instdto.setNext_device_code2(next_device_code2); |
|
||||
|
|
||||
instdto.setPriority(priority); |
|
||||
instdto.setInstruction_status(InstructionStatusEnum.READY.getIndex()); |
|
||||
instdto.setExecute_device_code(start_point_code); |
|
||||
instdto.setVehicle_type(vehicleType); |
|
||||
instdto.setAgv_system_type(agv_system_type); |
|
||||
instdto.setStart_height(start_height); |
|
||||
instdto.setNext_height(next_height); |
|
||||
|
|
||||
try { |
|
||||
instructionService.create(instdto); |
|
||||
} catch (Exception e) { |
|
||||
acsTask.setRemark("指令创建失败"); |
|
||||
taskService.updateByCodeFromCache(acsTask); |
|
||||
continue; |
|
||||
} |
|
||||
//创建指令后修改任务状态
|
|
||||
acsTask.setTask_status(TaskStatusEnum.BUSY.getIndex()); |
|
||||
taskService.update(acsTask); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,87 @@ |
|||||
|
package org.nl.system.service.quartz.task; |
||||
|
|
||||
|
import cn.hutool.core.util.ObjectUtil; |
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.alibaba.fastjson.JSONArray; |
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.nl.acs.agv.server.XianGongAgvService; |
||||
|
import org.nl.acs.ext.UnifiedResponse; |
||||
|
import org.nl.acs.instruction.domain.Instruction; |
||||
|
import org.nl.acs.instruction.service.InstructionService; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 定时查询AGV状态 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component("queryAGVStatus") |
||||
|
public class QueryAGVStatus { |
||||
|
|
||||
|
@Autowired |
||||
|
private XianGongAgvService agvService; |
||||
|
@Autowired |
||||
|
private InstructionService instructionService; |
||||
|
|
||||
|
public void run() { |
||||
|
try { |
||||
|
UnifiedResponse<JSONObject> resp = agvService.queryXZAgvInstStatus(JSONObject.class); |
||||
|
if (resp.isSuccess()) { |
||||
|
JSONObject data = resp.getData(); |
||||
|
JSONArray list = data.getJSONArray("list"); |
||||
|
if (list == null) return; |
||||
|
for (int i = 0; i < list.size(); i++) { |
||||
|
JSONObject one = list.getJSONObject(i); |
||||
|
String inst_code = one.getString("id"); |
||||
|
Instruction inst = instructionService.findByCodeFromCache(inst_code); |
||||
|
|
||||
|
if (ObjectUtil.isEmpty(inst)) continue; |
||||
|
|
||||
|
//子任务状态 待以后处理
|
||||
|
JSONArray blocks = JSONArray.parseArray(one.getString("blocks")); |
||||
|
for (int j = 0; j < blocks.size(); j++) { |
||||
|
JSONObject blocksjo = (JSONObject) blocks.get(j); |
||||
|
String blockId = blocksjo.getString("blockId"); |
||||
|
String device_code = blocksjo.getString("location"); |
||||
|
String state = blocksjo.getString("state"); |
||||
|
} |
||||
|
|
||||
|
String state = one.getString("state"); |
||||
|
if (!StrUtil.isEmpty(one.getString("vehicle"))) { |
||||
|
String carno = one.getString("vehicle"); |
||||
|
inst.setCarno(carno); |
||||
|
} |
||||
|
// 已创建=CREATED,
|
||||
|
// 待分配=TOBEDISPATCHED,
|
||||
|
// 正在执行=RUNNING,
|
||||
|
// 完成=FINISHED,
|
||||
|
// 失败=FAILED(主动失败),
|
||||
|
// 终止=STOPPED(被人为终止),
|
||||
|
// 无法执行=Error(参数错误),
|
||||
|
// 等待=WAITING
|
||||
|
//执行中
|
||||
|
if ("RUNNING".equals(state) || "CREATED".equals(state) || "TOBEDISPATCHED".equals(state) || "WAITING".equals(state)) { |
||||
|
if (inst != null) { |
||||
|
inst.setInstruction_status("1"); |
||||
|
instructionService.update(inst); |
||||
|
} |
||||
|
} else if ("FINISHED".equals(state)) { |
||||
|
if (inst != null) { |
||||
|
inst.setInstruction_status("2"); |
||||
|
instructionService.finish(inst); |
||||
|
} |
||||
|
} else if ("STOPPED".equals(state) || "FAILED".equals(state) || "Error".equals(state)) { |
||||
|
if (inst != null) { |
||||
|
inst.setInstruction_status("1"); |
||||
|
instructionService.update(inst); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue