Quantcast
Channel: 小蓝博客
Viewing all articles
Browse latest Browse all 3145

ModbusTCP与Ethernet/IP通讯详解

$
0
0

ModbusTCP与Ethernet/IP通讯详解

在工业自动化领域,ModbusTCPEthernet/IP是两种广泛应用的通信协议。它们分别代表了不同的通信标准和应用场景,但都在实现设备间的数据交换和控制中发挥着关键作用。本文将深入解析这两种协议的基本原理、应用场景、优缺点及其在实际项目中的实现方法,帮助读者全面了解并选择适合的通信协议。

目录

  1. 引言
  2. ModbusTCP概述

  3. Ethernet/IP概述

  4. ModbusTCP与Ethernet/IP的对比

  5. 实际应用场景分析

  6. 实现ModbusTCP通信

  7. 实现Ethernet/IP通信

  8. 常见问题与解决方法

  9. 最佳实践与建议
  10. 总结
  11. 附录

引言

随着工业自动化程度的不断提高,设备间的数据通信需求日益增长。ModbusTCPEthernet/IP作为两种主流的工业通信协议,各自拥有独特的优势和应用场景。选择合适的通信协议,不仅能提升系统的效率和可靠性,还能降低维护成本。本文旨在通过详尽的分析和实例,帮助工程师和技术人员更好地理解和应用这两种通信协议。

ModbusTCP概述

Modbus协议简介

Modbus协议最初由施耐德电气(Schneider Electric)在1979年开发,是一种简单、开放的通信协议,广泛应用于工业自动化领域。Modbus协议支持多种传输介质,包括串行通信(如Modbus RTU)和以太网通信(如ModbusTCP)。

ModbusTCP工作原理

ModbusTCP是在以太网基础上实现的Modbus协议版本,使用TCP/IP协议进行数据传输。其通信模型基于客户端-服务器(Master-Slave)架构,主要包括以下几个部分:

  1. 客户端(Master):发起通信请求,控制数据交换的流程。
  2. 服务器(Slave):响应客户端的请求,提供数据或执行操作。
  3. 数据帧结构:包括以太网头部、TCP头部、Modbus协议数据部分。

数据帧结构示意图

+----------------+----------------+----------------+-------------------+
| Ethernet Header| TCP Header     | Modbus Header  | Modbus Data        |
+----------------+----------------+----------------+-------------------+

解释

  • Ethernet Header:包括目的MAC地址、源MAC地址和类型字段。
  • TCP Header:包含源端口、目的端口、序列号、确认号等信息。
  • Modbus Header:包括事务标识符、协议标识符、长度字段和单元标识符。
  • Modbus Data:实际的Modbus命令和数据内容。

ModbusTCP的优势与局限

优势

  • 简单易用:协议结构简单,易于实现和调试。
  • 广泛支持:众多工业设备和软件支持Modbus协议。
  • 开放性强:无需授权,适用于各种应用场景。

局限

  • 性能有限:在高负载和复杂通信需求下,可能存在性能瓶颈。
  • 安全性较低:缺乏内置的安全机制,易受网络攻击。
  • 功能单一:主要用于数据读取和写入,缺乏高级控制功能。

Ethernet/IP概述

Ethernet/IP协议简介

Ethernet/IP(Ethernet Industrial Protocol)是基于以太网的工业通信协议,由开放互连公司(ODVA)开发。它采用CIP(Common Industrial Protocol)作为通信基础,支持实时数据交换和高级控制功能,广泛应用于自动化控制系统。

Ethernet/IP工作原理

Ethernet/IP采用客户端-服务器和生产者-消费者(Producer-Consumer)两种通信模型,具备高度的灵活性和扩展性。其核心组成部分包括:

  1. 设备(Devices):提供数据和服务的工业设备。
  2. 扫描器(Scanner):负责网络中的通信管理和数据交换。
  3. 数据模型:基于CIP,定义设备的属性、服务和功能。

通信模型示意图

+----------------+        +----------------+        +-----------------+
| Scanner        | <----> | Device 1       | <----> | Device 2        |
+----------------+        +----------------+        +-----------------+

解释

  • Scanner:类似于主站,管理与各设备的通信。
  • Device:作为从站,响应扫描器的请求并提供数据。

Ethernet/IP的优势与局限

优势

  • 高性能:支持实时数据交换,满足高负载和复杂应用需求。
  • 安全性强:内置多种安全机制,保障数据传输的安全性。
  • 功能丰富:支持高级控制功能,如运动控制和自动化流程管理。
  • 可扩展性强:灵活的架构设计,易于扩展和集成。

局限

  • 复杂性较高:协议结构复杂,配置和调试需要较高的技术水平。
  • 成本较高:实现高性能和安全性的功能可能增加系统成本。
  • 兼容性要求高:不同设备厂商的实现可能存在兼容性问题。

ModbusTCP与Ethernet/IP的对比

为了更好地理解这两种协议的异同,以下通过分析说明表进行对比:

特性ModbusTCPEthernet/IP
通信模型客户端-服务器(Master-Slave)客户端-服务器(Scanner-Device)
生产者-消费者
实时性一般
安全性较低
复杂性简单较高
功能数据读取和写入实时数据交换、高级控制功能
扩展性有限
成本较低较高
应用场景设备监控、简单控制高级自动化控制、实时系统
支持设备数量较多较少
配置难度

解释

  • 通信模型:ModbusTCP采用传统的Master-Slave模式,而Ethernet/IP支持更为复杂的通信模型,如生产者-消费者模式,适应更复杂的应用需求。
  • 实时性:Ethernet/IP在实时性方面表现更佳,适用于需要高实时数据交换的应用场景。
  • 安全性:Ethernet/IP内置多种安全机制,适合对数据安全性要求较高的应用,而ModbusTCP在安全性方面相对薄弱。
  • 复杂性与成本:Ethernet/IP因功能丰富和安全性高,配置和实现相对复杂,成本较高;而ModbusTCP则因其简单性,成本较低,适用于预算有限的项目。

实际应用场景分析

ModbusTCP的典型应用

  1. 设备监控:广泛应用于PLC、传感器、仪表等设备的实时监控与数据采集。
  2. 能源管理:用于监控和控制电力系统中的设备,如变压器、配电盘等。
  3. 楼宇自动化:在楼宇管理系统中,用于控制照明、空调等设备。
  4. 简单控制系统:适用于不需要复杂控制逻辑的工业控制系统。

Ethernet/IP的典型应用

  1. 高级自动化控制:应用于复杂的自动化生产线,支持多轴运动控制和精密控制。
  2. 实时数据交换:用于需要高频率和低延迟数据传输的系统,如机器人控制。
  3. 工厂自动化:集成多个复杂设备,支持复杂的生产流程管理和优化。
  4. 智能制造:在智能制造系统中,支持大数据采集和分析,实现生产优化和预测性维护。

实现ModbusTCP通信

实现ModbusTCP通信需要合理配置硬件和软件,确保设备间的数据交换顺畅。以下是详细步骤:

硬件需求

  1. 以太网设备:包括PLC、工业计算机、传感器等,支持以太网通信。
  2. 交换机/路由器:用于连接多个以太网设备,确保网络的稳定性和可靠性。
  3. 网络接口卡:确保设备具备以太网接口,支持TCP/IP协议。

软件配置

  1. 安装ModbusTCP库:根据开发语言选择合适的ModbusTCP库,如libmodbus(C语言)、pymodbus(Python)、Modbus4J(Java)等。
  2. 配置网络参数:确保设备的IP地址、子网掩码、网关等网络参数正确配置,保证设备间能够互相通信。
  3. 配置ModbusTCP参数:包括端口号(默认502)、超时时间、重试次数等,确保通信的稳定性和可靠性。

通信示例

以下以Python语言为例,使用pymodbus库实现ModbusTCP客户端与服务器的通信。

安装pymodbus库

pip install pymodbus

解释

  • 使用 pip命令安装pymodbus库,这是一个纯Python实现的Modbus协议库,支持客户端和服务器模式。

ModbusTCP服务器示例

from pymodbus.server.sync import StartTcpServer
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.datastore import ModbusSequentialDataBlock

# 配置数据存储
store = ModbusSlaveContext(
    di = ModbusSequentialDataBlock(0, [0]*100),
    co = ModbusSequentialDataBlock(0, [0]*100),
    hr = ModbusSequentialDataBlock(0, [17]*100),
    ir = ModbusSequentialDataBlock(0, [17]*100))
context = ModbusServerContext(slaves=store, single=True)

# 启动服务器
StartTcpServer(context, address=("localhost", 5020))

解释

  • 数据存储配置:使用 ModbusSlaveContext定义离散输入(di)、线圈(co)、保持寄存器(hr)和输入寄存器(ir)的初始值。
  • 服务器上下文:将数据存储配置封装到 ModbusServerContext中,single=True表示只有一个从站。
  • 启动服务器:调用 StartTcpServer启动ModbusTCP服务器,监听 localhost的5020端口。

ModbusTCP客户端示例

from pymodbus.client.sync import ModbusTcpClient

# 创建客户端并连接到服务器
client = ModbusTcpClient('localhost', port=5020)
client.connect()

# 读取保持寄存器
result = client.read_holding_registers(0, 10, unit=1)
print(result.registers)

# 写入保持寄存器
client.write_register(1, 10, unit=1)

# 关闭连接
client.close()

解释

  • 客户端连接:创建 ModbusTcpClient对象,连接到 localhost的5020端口。
  • 读取保持寄存器:调用 read_holding_registers方法,读取地址0开始的10个寄存器的值。
  • 写入保持寄存器:使用 write_register方法,将值10写入地址1的保持寄存器。
  • 关闭连接:完成通信后,关闭客户端连接。

配置说明

  1. 服务器配置

    • 数据块初始化:设置各类寄存器的初始值,确保客户端能够正确读取和写入数据。
    • 监听地址与端口:选择合适的IP地址和端口号,避免与其他服务冲突。
  2. 客户端配置

    • 连接参数:确保客户端的IP地址和端口与服务器匹配。
    • 操作类型:根据需要选择读取或写入不同类型的寄存器或线圈。

实现Ethernet/IP通信

Ethernet/IP通信涉及更为复杂的配置和高级功能,以下是详细的实现步骤:

硬件需求

  1. Ethernet/IP兼容设备:如Allen-Bradley PLC、工业以太网交换机等,支持Ethernet/IP协议。
  2. 高性能以太网设备:确保网络设备具备高带宽和低延迟,满足实时通信需求。
  3. 网络接口卡:确保设备具备以太网接口,支持高效的数据传输。

软件配置

  1. 安装Ethernet/IP库:根据开发语言选择合适的Ethernet/IP库,如OpENer(C语言)、libplctag(C++)、EtherNet/IP for Python等。
  2. 配置网络参数:设置设备的IP地址、子网掩码、网关等,确保设备间网络通信正常。
  3. 配置Ethernet/IP参数:包括连接类型(Explicit Explicit vs Implicit)、设备描述符、IO映射等,确保数据交换的正确性和效率。

通信示例

以下以Python语言为例,使用 cpppo库实现Ethernet/IP客户端与服务器的通信。

安装cpppo库

pip install cpppo

解释

  • 使用 pip命令安装cpppo库,这是一个支持Ethernet/IP协议的Python库,适用于开发客户端和服务器应用。

Ethernet/IP服务器示例

from cpppo.server.enip import se
from cpppo.server.enip import service
from cpppo.server.enip import rpi

# 定义设备属性和IO映射
class MyDevice(rpi.Element):
    def __init__(self):
        super(MyDevice, self).__init__()
        self._name = "MyEthernetIPDevice"
        self._data = [0] * 10  # 简单的IO映射,10个整数

    def get_attr_single(self, attr_id):
        if attr_id == 0x01:
            return self._name
        elif attr_id == 0x02:
            return self._data
        else:
            return None

# 启动Ethernet/IP服务器
device = MyDevice()
service.start(device, interface='0.0.0.0', port=44818)

解释

  • 设备定义:创建 MyDevice类,继承自 rpi.Element,定义设备名称和简单的IO映射。
  • 属性获取:实现 get_attr_single方法,根据属性ID返回设备名称或数据。
  • 启动服务器:调用 service.start方法,启动Ethernet/IP服务器,监听所有网络接口的44818端口(Ethernet/IP标准端口)。

Ethernet/IP客户端示例

from cpppo.client.enip import scan
from cpppo.server.enip import rpi

# 扫描网络中的Ethernet/IP设备
devices = scan.find_devices()
print("Found devices:", devices)

# 连接到特定设备并读取IO数据
for device in devices:
    connection = rpi.Connection(device[0], device[1])
    data = connection.read_attr_single(0x02)
    print("Device:", device, "Data:", data)

解释

  • 设备扫描:使用 scan.find_devices方法扫描网络中的Ethernet/IP设备,返回设备的IP地址和端口。
  • 连接与读取:遍历找到的设备,创建连接对象,并调用 read_attr_single方法读取IO数据(属性ID 0x02)。
  • 输出结果:打印设备信息和读取的数据。

配置说明

  1. 服务器配置

    • 设备属性定义:根据应用需求定义设备的属性和数据结构,确保客户端能够正确读取和写入数据。
    • IO映射:合理规划IO映射,确保数据交换的高效性和实时性。
  2. 客户端配置

    • 设备扫描与发现:利用扫描功能,动态发现网络中的Ethernet/IP设备,便于设备管理和数据交换。
    • 数据读取与写入:根据应用需求,选择合适的属性ID进行数据交换,确保数据的准确性和实时性。

常见问题与解决方法

在实际应用中,ModbusTCP与Ethernet/IP通信可能会遇到各种问题。以下总结了一些常见问题及其解决方法:

ModbusTCP常见问题

问题1:无法建立连接

可能原因

  • 服务器未启动或监听端口错误。
  • 网络配置错误,客户端与服务器不在同一网络段。
  • 防火墙阻止了ModbusTCP端口(默认502)的通信。

解决方法

  1. 检查服务器状态:确保ModbusTCP服务器已启动并监听正确的端口。
  2. 验证网络配置:确认客户端和服务器的IP地址和子网掩码配置正确。
  3. 调整防火墙设置:开放ModbusTCP端口,允许客户端访问。

问题2:读取数据失败

可能原因

  • 请求的寄存器地址超出范围。
  • 服务器没有响应,导致超时。
  • 客户端配置错误,使用了错误的单元标识符。

解决方法

  1. 验证寄存器地址:确认请求的寄存器地址在服务器支持的范围内。
  2. 检查服务器响应:使用网络抓包工具(如Wireshark)检查服务器是否有响应。
  3. 确认单元标识符:确保客户端使用的单元标识符与服务器配置一致。

Ethernet/IP常见问题

问题1:设备扫描失败

可能原因

  • 设备未连接到网络或未配置Ethernet/IP协议。
  • 网络配置错误,扫描请求未能到达目标设备。
  • 防火墙或网络安全策略阻止了扫描请求。

解决方法

  1. 检查设备连接:确保所有Ethernet/IP设备已正确连接到网络,并配置了正确的IP地址。
  2. 验证网络配置:确认客户端和服务器在同一网络段,或路由配置正确。
  3. 调整防火墙设置:开放Ethernet/IP端口(默认44818),允许扫描请求通过。

问题2:数据读取异常

可能原因

  • 属性ID配置错误,导致无法正确读取数据。
  • IO映射不正确,导致数据不一致。
  • 设备响应延迟,导致读取超时。

解决方法

  1. 确认属性ID:检查客户端使用的属性ID是否与服务器定义的一致。
  2. 验证IO映射:确保服务器的IO映射配置正确,数据对应关系明确。
  3. 调整超时时间:根据网络状况和设备性能,适当增加读取超时时间。

最佳实践与建议

保持系统更新

建议

  • 定期更新ModbusTCP和Ethernet/IP协议库,获取最新的功能和性能优化。
  • 确保操作系统和网络设备固件保持最新版本,提升系统的安全性和稳定性。

合理规划网络架构

建议

  • 在工业环境中,采用专用的工业以太网交换机,隔离通信流量,减少网络拥塞。
  • 根据设备数量和通信需求,合理划分子网,优化网络拓扑结构。

安全性配置

建议

  • ModbusTCP:由于安全性较低,建议在安全的内网环境中使用,避免通过公共网络传输敏感数据。
  • Ethernet/IP:充分利用其内置的安全机制,如访问控制列表(ACL)、加密传输等,保障数据的安全性。

使用日志与监控

建议

  • 启用协议库的日志功能,记录通信过程中的详细信息,便于故障排查。
  • 采用网络监控工具,实时监控通信质量,及时发现并解决网络问题。

测试与验证

建议

  • 在部署前,进行全面的通信测试,验证数据交换的准确性和实时性。
  • 使用模拟器或测试工具,模拟实际应用场景,确保系统在不同条件下的稳定性。

文档与培训

建议

  • 详细记录通信配置、设备参数和操作步骤,便于后续维护和升级。
  • 对操作人员进行协议相关的培训,提高其对通信问题的识别和解决能力。

总结

ModbusTCPEthernet/IP作为工业自动化领域的两大通信协议,各自具备独特的优势和应用场景。ModbusTCP以其简单易用和广泛支持,适用于设备监控和简单控制系统;而Ethernet/IP则凭借其高性能和丰富功能,适用于高级自动化控制和实时数据交换。通过本文的详细解析和实际示例,读者能够全面了解这两种协议的工作原理、优势与局限,并掌握在实际项目中实现和优化它们的方法。

关键要点回顾

  • ModbusTCP

    • 简单易用,适用于基本数据交换。
    • 客户端-服务器架构,广泛支持多种工业设备。
    • 安全性较低,适合内网环境使用。
  • Ethernet/IP

    • 高性能,支持实时数据交换和高级控制功能。
    • 更复杂的通信模型,适用于复杂自动化系统。
    • 内置安全机制,适合对数据安全性要求高的应用。

通过合理选择和配置这两种通信协议,工程师能够构建高效、稳定和安全的工业自动化系统,满足不同项目的多样化需求。

附录

常用命令与配置表

命令/配置用途
sudo apt-get install libmodbus-dev安装ModbusTCP开发库(以Debian/Ubuntu为例)
find_package(OpenCV 4.5.0 REQUIRED)在CMake中查找并加载指定版本的OpenCV库
target_link_libraries(MyApp PRIVATE ${OpenCV_LIBS})将OpenCV库链接到CMake项目的目标可执行文件
pip install pymodbus使用pip安装pymodbus库(Python ModbusTCP库)
pip install cpppo使用pip安装cpppo库(Python Ethernet/IP库)
sudo dpkg --add-architecture i386启用32位架构支持(适用于在64位系统上运行32位程序)
sudo apt-get install libc6:i386安装32位GNU C库
sudo apt-get install libncurses5:i386安装32位NCurses库
sudo apt-get install libstdc++6:i386安装32位C++标准库
source /etc/environment重新加载环境变量配置
docker pull i386/ubuntu拉取32位Ubuntu的Docker镜像
docker run -it i386/ubuntu /bin/bash运行32位Ubuntu的Docker容器并进入Shell

协议对比表

特性ModbusTCPEthernet/IP
通信模型客户端-服务器(Master-Slave)客户端-服务器(Scanner-Device)
生产者-消费者
实时性一般
安全性较低
复杂性简单较高
功能数据读取和写入实时数据交换、高级控制功能
扩展性有限
成本较低较高
应用场景设备监控、简单控制高级自动化控制、实时系统
支持设备数量较多较少
配置难度

示例CMakeLists.txt解释表

CMakeLists.txt片段说明
cmake_minimum_required(VERSION 3.10)指定CMake的最低版本要求,确保使用的CMake功能兼容。
project(MyOpenCVProject)定义项目名称为 MyOpenCVProject
find_package(OpenCV 4.5.0 REQUIRED)查找并加载OpenCV 4.5.0版本,若未找到则配置失败。
if(OpenCV_VERSION VERSION_LESS "4.5.0")检查找到的OpenCV版本是否低于4.5.0。
message(FATAL_ERROR "OpenCV version 4.5.0 or higher is required. Found version ${OpenCV_VERSION}.")如果版本低于4.5.0,输出错误信息并停止配置过程。
add_executable(MyApp main.cpp)定义一个名为 MyApp的可执行文件,源文件为 main.cpp
target_link_libraries(MyApp PRIVATE ${OpenCV_LIBS})将OpenCV库链接到 MyApp可执行文件。
set(OpenCV_DIR "/usr/local/share/OpenCV")设置OpenCV的CMake配置文件路径为 /usr/local/share/OpenCV
find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui)查找并加载OpenCV的 coreimgprochighgui模块。
include(FindOpenCV.cmake)包含自定义的FindOpenCV模块文件,便于扩展或自定义查找逻辑。

常见错误表

错误信息可能原因解决方法
Could not find package 'OpenCV'OpenCV未安装或路径未配置安装OpenCV,或设置 OpenCV_DIR变量指向正确路径
OpenCV version 4.5.0 or higher is required. Found version 4.2.0.系统中安装的OpenCV版本低于需求升级OpenCV至4.5.0或更高版本
Undefined reference to 'cv::imread'未正确链接OpenCV库确认 target_link_libraries中包含 ${OpenCV_LIBS}
Package 'OpenCV' found but required components are missing: coreOpenCV安装不完整,缺少必要模块重新安装OpenCV,确保包含所有必需的模块
Cannot open include file: 'opencv2/opencv.hpp': No such file or directory缺少OpenCV的头文件安装OpenCV开发库,或检查头文件路径是否正确
CMake Error at CMakeLists.txt:20 (message): OpenCV version 4.5.0 or higher is required.版本检查逻辑错误检查CMakeLists.txt中的版本检查逻辑,确保变量正确设置

关键命令解释表

命令/代码片段说明
find_package(OpenCV 4.5.0 REQUIRED)在CMake中查找并加载OpenCV 4.5.0版本,若未找到则配置失败。
if(OpenCV_VERSION VERSION_LESS "4.5.0")检查找到的OpenCV版本是否低于4.5.0。
message(FATAL_ERROR "OpenCV version 4.5.0 or higher is required. Found version ${OpenCV_VERSION}.")如果版本低于4.5.0,输出错误信息并停止配置过程。
add_executable(MyApp main.cpp)定义一个名为 MyApp的可执行文件,源文件为 main.cpp
target_link_libraries(MyApp PRIVATE ${OpenCV_LIBS})将OpenCV库链接到 MyApp可执行文件。
set(OpenCV_DIR "/usr/local/share/OpenCV")设置OpenCV的CMake配置文件路径为 /usr/local/share/OpenCV
find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui)查找并加载OpenCV的 coreimgprochighgui模块。
include(FindOpenCV.cmake)包含自定义的FindOpenCV模块文件,便于扩展或自定义查找逻辑。
pip install pymodbus使用pip安装pymodbus库(Python ModbusTCP库)。
pip install cpppo使用pip安装cpppo库(Python Ethernet/IP库)。
sudo dpkg --add-architecture i386启用32位架构支持(适用于在64位系统上运行32位程序)。
sudo apt-get install libc6:i386安装32位GNU C库。
sudo apt-get install libncurses5:i386安装32位NCurses库。
sudo apt-get install libstdc++6:i386安装32位C++标准库。
source /etc/environment重新加载环境变量配置。
docker pull i386/ubuntu拉取32位Ubuntu的Docker镜像。
docker run -it i386/ubuntu /bin/bash运行32位Ubuntu的Docker容器并进入Shell。
pip install pymodbus安装pymodbus库,支持ModbusTCP客户端和服务器开发。
from pymodbus.client.sync import ModbusTcpClient导入ModbusTCP客户端类。
client = ModbusTcpClient('localhost', port=5020)创建ModbusTCP客户端对象,连接到本地5020端口。
client.connect()建立与ModbusTCP服务器的连接。
result = client.read_holding_registers(0, 10, unit=1)读取从站1地址0开始的10个保持寄存器的值。
client.write_register(1, 10, unit=1)向从站1的地址1写入值10。
from cpppo.client.enip import scan导入Ethernet/IP设备扫描功能。
devices = scan.find_devices()扫描网络中的Ethernet/IP设备。
connection = rpi.Connection(device[0], device[1])创建与Ethernet/IP设备的连接对象。
data = connection.read_attr_single(0x02)读取设备属性ID 0x02的数据。
from cpppo.server.enip import service导入Ethernet/IP服务器服务模块。
service.start(device, interface='0.0.0.0', port=44818)启动Ethernet/IP服务器,监听所有接口的44818端口。

通过上述附录中的常用命令和代码解释表,用户可以快速查找和应用所需的命令和代码,解决在实现ModbusTCP与Ethernet/IP通信时遇到的各种问题,确保项目的稳定性和功能的正常运行。


Viewing all articles
Browse latest Browse all 3145

Trending Articles