本篇为flexsim基础操作及socket通信搭建。本文纯手打,图片均为实操截图,转载须注明出处。
本文将介绍flexsim的基础操作、简单函数以及socket通信。本文不涉及的部分,读者可以有针对性地查询用户手册。
相关链接
- socket通信:blog.csdn.net/weixin_4746…
- flexsim教程:www.bilibili.com/video/BV11E…
- 项目地址:github.com/littlehole/…
flexsim基础
- 软件就长这样
左边显示的新建和打开应该都懂吧。现在新建一个模型,默认参数直接ok进去就行,预期界面如下。
资源栏
library是资源库,toolbox是工具箱。
如果你不小心关掉了这两栏,toolbox可以从选项栏view里打开,也可以直接点选项栏下边的快捷栏里的tools打开。library从view选项里open default workspace打开。
参数栏
在右边会显示你选中资源的参数信息。如果你双击某个资源,能看到更详细的界面。右边的简略参数信息,只是为了让你看看当前资源的状态,做一些摆放调整操作,这样会更方便。
快捷栏
- 这些栏目的命名都是我自己随便打的,实际怎么分需要去看官方的文档,我只是做一个全面简洁的介绍。
- 第一行:
- 新建、打开、保存
- 标准指针,建立连接,取消连接(后两者基本不用,有快捷键)
- 其他(自己点开看看就知道了)
- 第二行:
- reset:重置整个模型
- run:开始
- stop:停止
- step:跨到下一步(一般也不用,调时间就行了,除非你要很细致)
- run time:显示运行的时间(不是真实时间,是模型运行的‘时间’)
- run speed:设置模型运行的速度,自行调整
连接操作
- A连接:
- A连接是对应输入输出关系的连接,可用快捷栏里的建立连接,选择对应的A连接即可使用。除此之外,还有快捷键A,按住A键,依次点击两个实例资源,即刻达成前者向后者输出的连接。
- 取消A连接,可以使用快捷栏里的取消连接,也可以按住快捷键Q,依次点击两个实例,就能够取消A连接。
- S连接:
- S连接是实例与执行器之间的连接。若要达成s从a运输box达到b(或是a使用s运输box到b)的效果,可以使用S连接,从a连接到s。
- 与A连接相同,取消S连接,按住快捷键W即可。
- D连接:
- 跟网络节点有关。
- 取消D连接,按住E快捷键即可。
下面是实操展示。
拉出一个source实例和一个queue实例,使用a连接从source连接至a,点击运行run。需要等待一段时间,queue才会出现box。这是因为source的初始设置里,到达时间是exponential函数,越来越快,但在run speed初始设置为4的时候,就有点慢了,可以勾选arrival at time 0,也可以调整速度,还可以点击step。
尝试各种操作,你可以更快地熟悉flexsim。
最后的结果,就如下图所示。当这条输出成功导通后,你可以发现连接在queue上的三角标已经变成了绿色。
之后,我们从task executers中拉出一个operator,使用s连接从source连到operator,他将成为我们的苦力。点击运行run,等待片刻,你会发现他并不干活。其原因是source并未选择调用执行器。我们需要选中source,在右侧属性的output栏目,亦或是双击source,在属性窗口中点击flow标签找到output栏目。勾选use transport即可。
reset之后run,就能看到operator从source搬运box至queue,并且一直重复该项工作。
一个基本的模型就已经实现了。
library
library里的所有实例资源,都是可以拖拽到模型里的。初学者建议实操看看样式。
下面将对部分常用资源进行说明,未提及的资源请查操作手册或者百度。
fixed resources
-
source:item流入的源头,你可以设置流入的东西是什么。
- 如图所示,source的属性有五个标签页,下面介绍常用的四个标签页及其常用功能。
- source
- arrival style:到达方式(一般为到达时间间隔)
- flowitem class:流中的物件类型,也就是从source流出来的item类型,默认是box,也就是盒子。
- arrival at time 0:0时间到达,也就是你run起来,就会有一个item出来,不需要去等。
- inter-arrivaltime:到达时间间隔,默认是指数函数。下拉菜单也提供了很多其他函数的选项,但不要拘泥于函数,直接往里写个常数或是变量,也许更适合实际需求。这与后续的代码部分有一定关联。
- 函数部分不多赘述,自行探索。
- flow
- ouput:指明source流向。
- send to port:默认为first available,即第一个可用。也就是你先连的哪个,就用哪个。在下拉菜单中,也可以设置多种传输规则,比如按照item的属性、随机、百分比等。
- use transport:使用运输工具。
- triggers
- 该页面默认为空,需要自行增设触发器。该功能非常重要,后面单列讲解。
- general
- 主要关注ports中的input和output,其会展示出入端口。其他内容可在右侧属性栏进行调整,无需专门在此调整。
- 如图所示,source的属性有五个标签页,下面介绍常用的四个标签页及其常用功能。
-
queue:队列,所有的item到这儿都会暂存起来。
- queue
- maximum content:最大库存
- LIFO:last in first out 后进先出
- batching:批量操作
- visual:视觉效果
- flow
- output同source
- input类似ouput,可自行摸索。
- queue
-
processor:处理器,可以对item进行各种处理。
- processor
- setup time:安装时间
- process time:处理时间
- processor
-
sink:流出/回收。
- sink
- recycling:设置回收的方式。
- sink
-
combiner:合并/组装器,对多条item流进行打包或组装。
- combiner
- combine:设置组合的方式
- pack:打包
- join:连接
- batch:分组
- components list:组成成分的比例
- 默认是input port1 传输过来的item是1;
- 设置其他input port的量,即可做到1:n的比例进行打包。上图为1:5进行打包。
- combine:设置组合的方式
- combiner
-
separator:拆分器,需要与combiner配套使用。
- unpack:解包
- split:分解
task executers
- operator
- transport
- robot
- ASRSvehicle
conveyors
- straight conveyor:传送带
- 两个传送带只要放在一起,就会自动拼接。
- 传送带有直线和曲线两种,按需使用即可。
warehousing
-
rack:货架。
- storage object
- 设置一些存储参数
- dimensions
- 调整货架的大小
- number of bays:一行几格
- number of levels:一行几层
- slots per bay:一格有几个槽位
- storage object
triggers
如上图所示,processor的触发器是有很多种的。下面介绍最常用的触发on entry(item流入时)、on exit(item流出时)、on message(接收信息时)。至于on reset,因为有全面的reset设置,因此博主没有特别对实例设置该触发。
-
on entry / on exit
- 在触发设置中,flexsim已经为使用者提供了许多预设好的选择。每一种设置看名字就能知道大概。
- data:主要是设置item的label,但在设置之前,需要新增自定义的label。后面会对label设置进行讲解。
- visual:设置item的视觉表现,例如换色等。当我们需要一个source发出几种不同颜色box时,就需要设置visual。
- 其余自行探索。
-
on message
- on message触发中,预设选项与前两者不同,主要是对ports的控制以及shape的改变。通过这些设置,我们可以实现流入流出的停止与开始,以及模拟“加工改造”。
- 但光靠预设,可能无法满足我们的需求,因此我们要学习flexsim内置触发的代码结构。
-
trigger code:点击触发栏目的“书页”(+×的后面)按钮,就可以打开code页面。
- 基础变量:on entry/exit
Object current = ownerobject(c); // current表示当前的实例,比如你设置source的触发,那么current就表示source。 Object item = param(1); // item就是从current流经 int port = param(2); // port就是端口 // 博主没有使用过port参数,各位可以自行探索。至于如何控制端口的开关,有其他函数。- 基础变量:on message
Object current = ownerobject(c); // 同上 Object fromObject = param(1); // fromObject:消息从哪个实例来,对应发布消息的实例 Variant msgparam1 = param(2); // 参数,对应sendmessage()函数。 Variant msgparam2 = param(3); Variant msgparam3 = param(4); // 使用sendmessage需要指定发送的实例对象(实例的名字),随后传入参数,数量可自定。 // 接受message的实例,就需要设置on message的触发。- flexsim的函数,在网上没有看到比较好的整合。博主在学习过程中,主要信息来源于flexsim内置的函数声明(参数+返回)。
- flexsim支持写入脚本,支持c++和java。如果会用相关语言,可以尝试一二。而在触发的代码中,与c语言类似。每一句以分号;结尾,if(){}的判断结构依旧可以使用。
- 对于简单的仓储而言,最重要的数据记录,即进来多少出去多少,合格多少,不合格多少。在这些过程中,触发代码只需要设置变量,对其进行赋值与增减就行了。但我们都知道,局部变量是无法作用于全局的,所以在触发代码之外,我们还需要设置一些全局变量。
toolbox延伸
-
标签设置 - flowitem bin
- 在toolbox中,双击flowitem bin中的box。
- 在右侧的labels栏目中,点击“加号”,即可新增label。label其实就是box的属性,你可以新增数字、字符串、指针等属性。
- 在这里新增label后,你在模型设计中,就能针对该属性进行赋值、取值等操作。
- 知道这一步之后,你可以往后延伸出非常多的可能。
- 在toolbox中,双击flowitem bin中的box。
-
全局变量 - global variable
- 在toolbox中除了固定的内容外,还可以新增其他内容。在modeling logic中,我们可以新增全局变量。
- 设置全局变量的name、type,对其进行简短的描述,以及设定其初始值。建议在此设定好你需要的初始值,每次reset后,该值将重置为你设定的初始值。
-
模型触发 - model trigger
- 同样在modeling logic项目中,我们可以新增model trigger(模型触发)。在实例中,可以设置触发器;在模型中,也可以设置触发。
- on model open
- on reset
- on run start
- on run stop
- 选择on reset触发,里面是空白的,需要我们自行编写代码。
- 同样在modeling logic项目中,我们可以新增model trigger(模型触发)。在实例中,可以设置触发器;在模型中,也可以设置触发。
项目介绍
在相关链接中,我已经附上了github地址。里边有建立好的flexsim模型文件,简单服务端的golang代码,若不用go语言,可直接使用exe文件打开本地服务器。
下面将从无到有,构建该项目。
模型介绍
该模型为简易的仓储模型。该仓库将接受服务器发送的数据,完成存储与输出的任务指标。其最终成品如下图。
从一个入口,可以输入红蓝绿三种颜色的box。对输入的box进行打标、质检、打包、赋rfid、按需存储、分拣、按需出仓。
- 模型从上位机接收参数:
- 输入的box数量
- 需求储存的pack数量
- 需求出库三色box数量
- 模型传递给上位机的参数:
- 打标总量
- 质检结果(合格/不合格数量)
- 存储的pack数量
- 存储的三色box数量
变量定义
- 接收变量
- input:将要入仓的box数量,inputsource只能输出这么多box。
- packNeed:需要存储的pack
- redNeed:需求出仓的red数量
- blueNeed:需求出仓的blue数量
- greenNeed:需求出仓的green数量
- 传递变量
- redNum:已经分拣的red存储数量
- blueNum:已经分拣的blue存储数量
- greenNum:已经分拣的green存储数量
- labelNum:入仓打标的数量(无丢货的情况,与input相等)
- unqNum:不合格
- quaNum:合格
- packNum:存储的pack数量
- 其他变量
- socket:int类型,socket连接的记录。
- RFID:赋RFID的数量
功能分解
- 输入模拟:生成三种颜色的box
- 对box赋type标签,在生成的时候对item.type赋值
- 根据item.type的值调整颜色
- 每生成一个box,input--,当input==0时,使用
closeoutput(current)关闭出口。
- 打标
- 在打标机的on exit触发中,更新labelNum,同时更新box的label标签即可。
- 注意:需要先新增box标签,设置名字为label。
- setlabelnum:设置item名为“label”的num型标签的值为labelNum。
- 质检
- 质检只能模拟,无法真的去检测什么。
- 我们可以按概率,设置10%的不合格率。设置如下图。
- 记录合格与不合格,只需要在后面的queue中设置on entry触发,在触发中更新对应的全局变量即可。
-
打包
- combiner的两个输入,port1为pallet托盘,port2为box,比例为1:8。如何设置已经在上文中介绍,不多赘述。
-
RFID
- 与打标类似,唯一需要注意的,就是给pallet新增一个对应的标签。
-
pack存入rack
- 在采用传送带进行传递时,可能会遇到一点问题,因为点击传送带打开其属性,标签页基本都是空的,怎么都找不到use transport,也就导致s连接连了没用。但实际上,使用传送带作为输入时,我们要以传送带上的小方块作为对象。
- 如下图,与堆垛机进行s连接时,一定要记得是用白色方块进行连接,然后双击方块,就能找到你要的内容。
- pack出入库
- 入库时,packNum++;
- 出库时,packNum--;
- 可添加判断
- 若packNum<=packNeed,关闭出口;
- 否则,打开出口。
-
拆包
- 不需要设置太多,连好出入端口就行。
-
叉车分拣
- queue连接了三个rack;
- 根据item的type设置output。
-
box按需出仓
- 三个rack基本一致
- 出入更新redNum即可
- 在出库的时候,redNeed--,当其为0时,关闭出口。
至此,我们已经完成了模型的搭建。只需要设定好对应的变量,即便不进行socket通信,整个模型也能正常的离线运行。
而对于socket通信,我们需要考虑以下两点。
- 如何建立socket连接;
- 如何时刻收发信息。
socket通信
要达成flexsim与上位机的socket通信,首先要搭建本机服务器。博主使用golang简单搭建了一个本地服务器。
func main() {
fmt.Println("服务器开始监听")
listen, err := net.Listen("tcp", "0.0.0.0:8888")
if err != nil {
fmt.Println("listen err=", err)
return
}
defer listen.Close()
//循环等待客户端来连接
for {
fmt.Println("等待客户端来连接")
conn, err := listen.Accept()
if err != nil {
fmt.Println("Accept err=", err)
} else {
fmt.Printf("Accept suc con=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String())
}
go process(conn)
}
}
使用net包的listen方法,在8888端口建立tcp连接。在for循环中等待连接,连接成功后开启一个process协程。
func process(conn net.Conn) {
defer conn.Close()
conn.Write([]byte(fmt.Sprintf("input%dpack%dred%dblue%dgreen%d", input, packNeed, redNeed, blueNeed, greenNeed)))
for {
//创建一个切片
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("the connetction is closed")
conn.Close()
} else {
fmt.Printf("Read Error: %s\n", err)
}
return
}
//3.显示客户端发送的内容到服务器的终端
fmt.Printf("客户端:%s 发送信息:%s。\n", conn.RemoteAddr().String(), string(buf[:n]))
}
}
在process协程中,处理刚刚进行的连接。博主没有做可视化界面,也没有做实时输入的功能,只在最开始连接的时候向客户端发送指定参数,随后不断接收客户端发来的信息。
在onModelRest触发中,写入下列代码。
clientclose(socket);
socketinit();
socket = clientcreate();
if(clientconnect(socket, "127.0.0.1",8888))
{
pt("Flexsim客户端连接成功\n");
string receivemsg = clientreceive(socket,NULL,100,1); //保存从物理模型接收到的信息
string num = stringcopy(receivemsg,6,3);
input = stringtonum(num);
num = stringcopy(receivemsg,13,2);
packNeed = stringtonum(num);
num = stringcopy(receivemsg,18,2);
redNeed = stringtonum(num);
num = stringcopy(receivemsg,24,2);
blueNeed = stringtonum(num);
num = stringcopy(receivemsg,31,2);
greenNeed = stringtonum(num);
print(input);
}
else {
pt("连接失败\n");
return 0;
}
先关闭socket对应的连接,初始化socket,新建连接指向socket,检测连接。连接成功后pt打印字符串。通过使用clientreceive、stringcopy、stringtonum三个函数对字符串进行处理。其中stringcopy提示被弃用,可换用string。substr。不换也能用。而且这两者有一个缺点,即都需要指定长度。这限制了取值的位置,颇为不便。博主没有发现其他更好的办法,留待读者去发掘了。
搞定了socket连接,只要开启服务端,reset时便可建立socket连接。接下来便是构建持续传递信息的结构。
可采取以下结构,source指向sink,设置时间间隔100。这样每过100时间,就能触发一次on exit触发器,调用clientsend向服务端发送字符串信息。最后,服务端打印接收的信息。
上述方法其实就是在某个触发中使用clientsend函数,读者可以根据模型情况,去构建消息传递结构,或者直接放在某个实例中也可。
结语
希望本文能对你有帮助。