性能测试篇:六:locust性能工具使用

1:locust简介  locust官方学习网址:  https://docs.locust.io/en/stable/

Locust是一个用于可扩展的,分布式的,性能测试的,开源的,用Python编写框架/工具,它非常容易使用,也非常好学    
它的主要思想就是模拟一群用户将访问你的网站。每个用户的行为由你编写的python代码定义,同时可以从Web界面中实时观察到用户的行为。
Locust完全是事件驱动的,因此在单台机器上能够支持几千并发用户访问。    locust是基于协程的,异步执行代码的
与其它许多基于事件的应用相比,Locust并不使用回调,而是使用gevent,而gevent是基于协程的,可以用同步的方式来编写异步执行的代码。
每个用户实际上运行在自己的greenlet(协程)中

 2:locust特点

1:不需要编写笨重的UI或者臃肿的XML代码,基于协程而不是回调,脚本编写简单易读
2:有一个基于web简洁的HTML+JS的UI用户界面,可以实时显示相关的测试结果;
3:支持分布式测试,用户界面基于网络,因此具有跨平台且易于扩展的特点;
4:所有繁琐的I/O和协同程序都被委托给gevent,替代其他工具的局限性;

3:进程、线程和协程的区别

1、进程
  进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
  每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,
  所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。  
2、线程
  线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.
  线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),
  但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。  
3、协程
  协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。
  协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,
  直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

打开一个软件就是一个进程
一个进程下多个线程,比如多个qq聊天,cpu调度的最小单位,  jmeter使用多线程的方式

4:locust与jmeter等其他性能压测工具的区别 

工具      区别
jmeter    需要在UI界面上通过选择组件来“编写”脚本,模拟的负载是线程绑定的,  使用的是线程
        意味着模拟的每个用户,都需要一个单独的线程。单台负载机可模拟的负载数有限
locust    通过编写简单易读的python代码完成测试脚本,基于事件,同样配置下,单台负载机可模拟的负载数远超jmeter  协程更小,负载数远超jmeter的使用

jmeter和locust都是开源的
PS:但locust的局限性在于,目前其本身对测试过程的监控和测试结果展示,不如jmeter全面和详细,需要进行二次开发才能满足需求越来越复杂的性能测试需要。
1:开源许可证
  工具许可范围的问题是最重要的问题之一,因为您可能想知道是否需要支付额外的第三方工具来完成负载测试。
    如果某个工具是开源的,那么您几乎可以实现为性能测试设置的任何目标,而无需任何额外付款。 开源JMeter和Locust也不例外
   JMeter和Locust都提供了许可软件许可证,该许可证支持免费软件,对软件的分发方式提出最低要求。
    JMeter是由Apache开发的,它基于Apache License 2.0,
    而Locust是由一个由社区驱动的开发人员组成的小团队开发的基于MIT许可证。在这两种情况下,这些工具都是开源的,允许您自由使用它们,而不受任何使用限制

2:
负载测试创建和维护
  性能测试工作流程有三个主要步骤:创建,运行和分析。 一般第一步是最耗时的。
  编写JMeter性能测试的最常用方法
是使用其GUI模式。不需要写代码,简单,JMeter GUI模式提供了一个桌面客户端,允许您轻松创建测试,而无需编写单行代码(直到您需要创建棘手的测试),所以最简单的场景可能如下所示:
  JMeter非常简单,通常,即使是没有经验的工程师也可以毫无困难地上手。
    但是如果需要,您可以使用Java在GUI和非GUI模式下使用代码。 但是,由于脚本实现的复杂性(因为JMeter旨在与GUI模式一起使用)以及缺乏如何制作此类脚本的文档,因此这种方式在JMeter社区中并不流行
  Locust则需要python编程基础。代码设计请求:参数化,集合点等都需要通过代码实现

3:支持的协议
  理想情况下,您应该能够使用尽可能少的工具测试所有工具,只要它不会影响测试质量。
  使用JMeter,您可以使用完整的内置函数和第三方插件,在一个地方创建所有内容的性能测试。
    
您无需编码即可测试不同的协议甚至数据库。 这些包括JDBC,FTP,LDAP,SMTP等。JMeter还可以通过jar包扩展,比如加载jython,jmeter里可以使用python脚本。
  根据文档,Locust主要用于基于HTTP Web的测试。但可以扩展其默认功能并创建自定义Python函数来测试可以使用Python编程语言进行测试的任何内容。

4:并发用户数
  JMeter和Locust有完全不同的方式来处理机器资源。JMeter有一个基于线程的模型,它为每个用户分配一个单独的线程。
    每个步骤的线程分配和基准测试需要大量资源,这就是为什么JMeter对于您可以在一台机器上模拟的用户数量非常有限的原因。
    您可以在一台计算机上运行的用户数取决于许多因素,如脚本复杂性,硬件,响应大小等。 如果您的脚本很简单,
    JMeter允许您在一台机器上运行多达数千个,但脚本执行逐渐变得不可靠
  Locust有完全不同的用户模拟模型,它基于事件和异步方法(协程),以gevent coroutine作为整个过程的基石。
    这种实现允许Locust框架在一台机器上轻松模拟数千个并发用户,即使是在非常规的笔记本电脑上,也可同时运行内部有许多步骤的复杂测试

jmeter一般机器大概也就跑个1000左右线程就不行了,需要分布式了
locust可以并发更大的用户数

5:增强灵活性
  这两个工具提供相对相同的生成负载的方式 - 您可以指定在性能测试期间要使用的用户数以及它们应该加速的速度
  在JMeter中,您可以在指定字段的“线程组”控制器中配置负载:但是JMeter还有其他插件,可以让您配置非常灵活的负载。
    最好的方法之一是使用Ultimate Thread Group ,它允许用户制作非常具体的加载模式:
  Locust有不同的方法。 当您运行性能脚本时,Locust会自动在http://localhost:8089上启动Web界面的服务器,
    该界面为您提供仅指定线性负载的输入元素, 当然也可以命令行执行通过参数定制。

6:脚本录制
  这是JMeter具有强大优势的地方,因为它具有脚本录制的内置功能,而Locust根本没有此功能。
    
除此之外,还有许多第三方插件可以为JMeter制作脚本录制。 记录此类脚本最方便的方法之一是使用BlazeMeter chrome扩展。

7:测试监控
  JMeter和Locust都提供了强大的内置功能来监控性能脚本并分析您的测试结果。 JMeter有许多不同的元素叫做监听器。
    
每个侦听器都提供特定类型的监视,你也可以使用许多现有的自定义监听器扩展默认库。另一方面,JMeter监听器在其运行的机器上消耗大量资源。
    
这就是为什么通常,JMeter是以非GUI模式执行的,没有任何监听器或监控过程,在这种情况下,可使用3方工具,如BlazeMeter 。
  Locust的监测能力稍弱,不过几乎提供了所有可用于监控基本负载的信息。在脚本运行期间,Locust运行一个简单的Web服务器
  

java里暂时没有协程概念,python支持协程所有支持大量的并发
jmeter 写java的jar包放入jmeter相当于二次开发了
locust二次开发:编写其他功能,继承一些所需要的类,开发一些新的功能

 5:locust的安装

locust使用python代码写的  前提:安装python以及pycharm

安装locust库
pip install locust -i http://pypi.douban.com/simple/   安装locust库
pip install locust -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com  如果上面下载不成功,这样下载即可,信任源下载
  安装成功后可以输入 pip show locust 命令查看是否安装成功,以及通过 locust -help 命令查看帮助信息。

安装 pyzmq 库:分布式需要使用这个库
If you intend to run Locust distributed across multiple processes/machines, we recommend you to also install pyzmq.
  如果打算运行Locust 分布在多个进程/机器,需要安装pyzmq.
  通过pip命令安装。
pip install pyzmq -i http://pypi.douban.com/simple/

 6:Locust主要由下面的几个库构成

1:gevent
  gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API
2:flask
  Python编写的轻量级Web应用框架。
3:requests
  Python Http库
4:msgpack-python
  MessagePack是一种快速、紧凑的二进制序列化格式,适用于类似JSON的数据格式。msgpack-python主要提供MessagePack数据序列化及反序列化的方法。
5: six
  Python2和3兼容库,用来封装Python2和Python3之间的差异性
6: pyzmq
  pyzmq是zeromq(一种通信队列)的Python绑定,主要用来实现Locust的分布式模式运行
  当我们在安装 Locust 时,它会检测我们当前的 Python 环境是否已经安装了这些库,如果没有安装,它会先把这些库一一装上。

 7:locust原理

locust为什么能够识别写的代码和运行?
  locust基于两个类,继承两个类才能实现模拟用户行为:
    TaskSet类:locust里面的类,继承TaskSet类,写一个类继承这个类之后locust才能识别它
      这个类主要是模拟请求的行为,任务
      主要是为了模拟用户端请求信息信息,@task装饰器让locust识别到哪个是我们的任务
      模拟用户的行为
    HttpUser类:locust里的类继承HttpUser类,用户类,这个类主要写一些用户的配置信息,比如Host主机地址
      思考时间,等一系列信息,通过继承HttpUser类来实现
      用户配置类
    这两个类他们之间怎么通信,有任务,有用户配置  需要桥梁
    这个桥梁  在用户类里有个tasks=[TaskSet的类]  用户信息有哪些任务,指向任务类,两类就可以连通了
    这两类又继承locust里面的类,所以locust命令也能去识别他们的信息      简单原理

 8:Locust类详细讲解

在Locust类中,具有一个client属性,它对应着虚拟用户作为客户端所具备的请求能力,也就是我们常说的请求方法。    client模拟客户端请求
  通常情况下,我们不会直接使用Locust类,因为其client属性没有绑定任何方法。
  因此在使用Locust时,需要先继承Locust类,然后在继承子类中的client属性中绑定客户端的实现类。对于常见的HTTP(S)协议,
  Locust已经实现了HttpUser(1.0之前使用HttpLocust)类,其client属性绑定了HttpSession类,而HttpSession又继承自requests.Session。
  因此在测试HTTP(S)的Locust脚本中,我们可以通过client属性来使用Python requests库的所有方法,包括GET/POST/HEAD/PUT/DELETE/PATCH等,
  调用方式也与requests完全一致。
  另外,由于requests.Session的使用,因此client的方法调用之间就自动具有了状态记忆的功能。
    常见的场景就是,在登录系统后可以维持登录状态的Session,从而后续HTTP请求操作都能带上登录态。
  而对于HTTP(S)以外的协议,我们同样可以使用Locust进行测试,只是需要我们自行实现客户端。
  在客户端的具体实现上,可通过注册事件的方式,在请求成功时触发events.request_success,在请求失败时触发events.request_failure即可。
    然后创建一个继承自Locust类的类,对其设置一个client属性并与我们实现的客户端进行绑定。后续,我们就可以像使用HttpUser类一样,测试其它协议类型的系统

client通过客户端请求服务器,捕获响应信息,设置一些断言信息  
HttpUser  配置一些用户信息
原理就是这样

在Locust类中,除了client属性,还有几个属性需要关注下:
  .tasks(1.0以下是task_set:       task哪些任务,继承了任务类,才能去跑取locust请求
    tasks = [WebsiteTasks] Collection of python callables and/or
    TaskSet classes that the Locust user(s) will run.指向一个TaskSet类的列表,
    TaskSet类定义了用户的任务信息,该属性为必填;**  
  ·max_wait/min_wait: 每个用户执行两个任务间隔时间的上下限(毫秒),具体数值在上下限中随机取值,若不指定则默认间隔时间固定为1秒;  思考时间,不设置默认1s
  ·host:被测系统的host,当在终端中启动locust时没有指定--host参数时才会用到;    主机地址或者域名  
  ·weight:同时运行多个Locust类时会用到,用于控制不同类型任务的执行权重。        权重,权重越大跑的越多

测试开始后,每个虚拟用户(Locust实例)的运行逻辑都会遵循如下规律:  继承taskset任务类里面有个on_start方法,和初始化一样,类似jmeter里setpu线程组,只执行一次,初始化数据 
    ·先执行WebsiteTasks中的on_start(只执行一次),作为初始化;  
    ·从WebsiteTasks中随机挑选(如果定义了任务间的权重关系,那么就是按照权重关系随机挑选)一个任务执行;  
    ·根据Locust类中min_wait和max_wait定义的间隔时间范围(如果TaskSet类中也定义了min_wait或者max_wait,以TaskSet中的优先),    思考时间
      在时间范围中随机取一个值,休眠等待;  
    ·重复2~3步骤,直至测试任务终止。

TaskSet  **类详细讲解**    脚本模拟用户行为发送请求,发送请求使用client属性
  性能测试工具要模拟用户的业务操作,就需要通过脚本模拟用户的行为。在前面的比喻中说到,TaskSet类好比蝗虫的大脑,控制着蝗虫的具体行为。
  具体地,TaskSet类实现了虚拟用户所执行任务的调度算法,
    包括规划任务执行顺序(schedule_task)、挑选下一个任务(execute_next_task)、
    执行任务(execute_task)、休眠等待(wait)、中断控制(interrupt)等等。
    在此基础上,我们就可以在TaskSet子类中采用非常简洁的方式来描述虚拟用户的业务测试场景,对虚拟用户的所有行为(任务)进行组织和描述,并可以对不同任务的权重进行配置。
  在TaskSet子类中除了定义任务信息,还有一个是经常用到的,那就是on_start函数。这个和jmeter里setup线程组一样,在正式执行测试前执行一次,
    主要用于完成一些初始化的工作。例如,当测试某个搜索功能,而该搜索功能又要求必须为登录态的时候,就可以先在on_start中进行登录操作;
    前面也提到,HttpUser使用到了requests.Session,因此后续所有任务执行过程中就都具有登录态了 

9:locust百度案例 

from locust import HttpUser
from locust import TaskSet
from locust import task

class Demo(TaskSet):
    """继承   定义任务类"""
    def on_start(self):
        print("开始执行")

    @task
    def bai_du(self):
        url = "/"  # 请求路径,除了主机地址后面的请求路径,主机地址可以在用户类里定义
        res = self.client.get(url, verify=False)  # 请求路径+去掉https认证    因为是https协议,verify=False去掉认证
        if res.status_code == 200:  # 判断响应状态码是否为200,200返回成功
            print("success")
        else:
            print("failed")

class WebSitUser(HttpUser):
    """继承   定义用户类,访问用户"""
    tasks = [Demo]      # 指定任务类
    host = "https://www.baidu.com"  # 指定主机地址
    min_wait = 2000
    max_wait = 3000
    # 指定思考时间,3s到2s之内随机选取一个时间充当思考时间

if __name__ == '__main__':
    import os
    os.system("locust -f demo.py")
"""
通过os模块运行,这里相当于cmd终端直接执行:locust -f demo.py
locust脚本运行的时候打开浏览器输入:
    http://localhost:8089/      登录locust web页面
"""

  输入:http://localhost:8089/  登录locust  web端图形界面后:

操作步骤
  并发用户数设置:4

  每秒启动用户数:2
  设置好后点击:start swarming
  压测的图表和监控情况如下:
压测完成后点击web页面右上角的:stop

这就是locust的一个简单的应用

Type: 请求的类型,例如GET/POST。
Name:请求的路径
request:当前请求的数量。
fails:当前请求失败的数量。
Median:中间值,单位毫秒,一半的服务器响应时间低于该值,而另一半高于该值。
90%ile (ms): 90%的平均响应时间
99%ile (ms): 99%的平均响应时间

Average:平均值,单位毫秒,所有请求的平均响应时间。
Min:请求的最小服务器响应时间,单位毫秒。
Max:请求的最大服务器响应时间,单位毫秒。
Average size (bytes):平均每个请求的大小,单位字节。
Current RPS:当前的rps,每秒的请求数
Current Failures/s:每秒请求失败数

点击charts可以查看图表信息: 

  一:吞吐量

   二:响应时间

   三:并发用户数

   4:失败的请求数量以及失败的原因

   5:抛出的异常

   6:下载数据

 12:locust电商秒杀案例压测脚本编写

案例场景分析:秒杀业务流程
秒杀功能场景:
  登录——》列出商品——》查看商品详情——》秒杀商品  步骤,性能测试的流程
locust秒杀脚本源码:
from locust import HttpUser
from locust import TaskSet
from locust import task
import hashlib
import random

class UserTask(TaskSet):
    """用户任务类,用户的行为"""

    def on_start(self):
        print("开始执行")

    def md5_pwd(self, pwd):
        md5 = hashlib.md5()
        md5.update(b"zr" + pwd.encode("utf8") + b"hg")
        return md5.hexdigest()

    @task
    def test_login(self):
        payload = {
            "mobile": "13588000999",
            "password": self.md5_pwd("111111")
        }
        # payload:请求体数据
        res = self.client.post(url="/login/login", data=payload, name="登录")  # 表单请求date传参,res响应对象
        # name="登录" 为了locust的web界面name字段展示名字为name
        self.token = res.json()["data"]
        # if res.status_code == 200:
        #     print("test_login success")
        # else:
        #     print("test_login failed")

    @task
    def test_get_goods_list(self):
        """获取商品列表请求"""
        res = self.client.get(url="/goods/Jlist")
        if res.status_code == 200:
            self.goods_id_list = [goods['id'] for goods in res.json()['data']]
            # print("test_get_goods_list success")
        else:
            # print("test_get_goods_list failed")
            pass

    @task
    def test_goods_detail(self):
        """获取商品详情"""
        self.goods_id = random.choice(self.goods_id_list)
        res = self.client.get(url=f"/goods/detail/{self.goods_id}")
        # print(res.json())

    @task
    def test_miaosha(self):
        """秒杀接口"""
        payload = {
            "goodsId": self.goods_id,
            "token": self.token
        }
        res = self.client.post(url="/miaosha/miaosha", data=payload)
        print(res.json())

class WebSitUser(HttpUser):
    """定义用户类,访问用户"""
    tasks = [UserTask]  # 绑定任务类
    host = "http://47.104.157.210:7080/"  # 指定主机地址
    min_wait = 2000
    max_wait = 3000
    # 指定思考时间,3s到2s之内随机选取一个时间充当思考时间

if __name__ == '__main__':
    import os
    os.system("locust -f miaosha.py")

 13:locust响应断言  with+catch_response

from locust import HttpUser
from locust import TaskSet
from locust import task
import hashlib

class UserTask(TaskSet):
    """用户任务类,用户的行为"""
    def on_start(self):
        print("0")
        payload = {
            "times": 1
        }
        res = self.client.post(url="/miaosha/reset3", data=payload)
        if res.json()["msg"] == "成功":
            print("商品重置成功")
        else:
            print("商品重置失败")

    def md5_pwd(self, pwd):
        md5 = hashlib.md5()
        md5.update(b"zr" + pwd.encode("utf8") + b"hg")
        return md5.hexdigest()

    @task
    def test_login(self):
        print("1")
        payload = {
            "mobile": "13588000999",
            "password": self.md5_pwd("111111")
        }
        # locust响应断言,返回的数据不等于预期需要断言失败并且在locust 的web页面展示
        # locust断言方式:with + catch_response
        # 加一些判断后res.success()+res.failure("登录失败")来进行locust的响应断言   和locust的web界面展示数据关联
        with self.client.post(url="/login/login", data=payload, name="登录", catch_response=True) as res:
            # catch_response=True   catch捕获到响应  默认是False
            if res.status_code == 200 and res.json()["msg"] == "成功":
                self.token = res.json()['data']
                res.success()  # 断言成功
            else:
                res.failure("登录失败")  # 断言失败写到错误率和错误里面,调用这个方法就是写到错误里面

    @task
    def test_get_goods_list(self):
        print(2)
        """获取商品列表请求"""
        with self.client.get(url="/goods/Jlist", name="获取商品列表", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("获取商品列表失败")

    @task
    def test_goods_detail(self):
        print(3)
        """获取商品详情"""
        with self.client.get(url=f"/goods/detail/1", name="获取商品详情", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("获取商品详情失败")

    @task
    def test_miaosha(self):
        print(4)
        """秒杀接口"""
        print(self.token)
        payload = {
            "goodsId": 1,
            "token": self.token
        }
        with self.client.post(url="/miaosha/miaosha", data=payload, name="秒杀接口", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("秒杀商品失败")

class WebSitUser(HttpUser):
    """定义用户类,访问用户"""
    tasks = [UserTask]  # 绑定任务类
    host = "http://47.104.157.210:7080/"  # 指定主机地址
    min_wait = 2000
    max_wait = 3000
    # 指定思考时间,3s到2s之内随机选取一个时间充当思考时间

if __name__ == '__main__':
    import os
    os.system("locust -f miaosha.py")

 14:locust参数化    参数化三种

1:循环(随机)取数据,数据可重复使用  list    列表
2:保证并发测试数据唯一性,循环取数据  queue  队列,先进先出,队列取完了会自动停止,取完了以后往里再加
3:保证并发测试数据唯一性,不循环取数据  queue  队列取完了后就退出
参数化方式一:使用list列表
from locust import HttpUser
from locust import TaskSet
from locust import task
import hashlib
from random import choice

class UserTask(TaskSet):
    """用户任务类,用户的行为"""

    def on_start(self):
        payload = {
            "times": 1
        }
        res = self.client.post(url="/miaosha/reset3", data=payload)
        if res.json()["msg"] == "成功":
            print("商品重置成功")
        else:
            print("商品重置失败")

    def md5_pwd(self, pwd):
        md5 = hashlib.md5()
        md5.update(b"zr" + pwd.encode("utf8") + b"hg")
        return md5.hexdigest()

    @task
    def test_login(self):
        user_info = choice(self.user.user_data).split(",")
        # self.user会关联到下面的用户类WebSitUser,可以获取里面的user_data数据拿到用户列表
        # 这里self.user.user_data关联到下面WebSitUser用户类的user_data属性
        payload = {
            "mobile": user_info[0],
            "password": self.md5_pwd(user_info[1])
        }
        print(payload)
        with self.client.post(url="/login/login", data=payload, name="登录", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                self.token = res.json()['data']
                res.success()  # 断言成功
            else:
                res.failure("登录失败")

    @task
    def test_get_goods_list(self):
        """获取商品列表请求"""
        with self.client.get(url="/goods/Jlist", name="获取商品列表", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("获取商品列表失败")

    @task
    def test_goods_detail(self):
        """获取商品详情"""
        with self.client.get(url=f"/goods/detail/1", name="获取商品详情", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("获取商品详情失败")

    @task
    def test_miaosha(self):
        """秒杀接口"""
        payload = {
            "goodsId": 1,
            "token": self.token
        }
        with self.client.post(url="/miaosha/miaosha", data=payload, name="秒杀接口", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("秒杀商品失败")

class WebSitUser(HttpUser):
    """定义用户类,访问用户"""
    tasks = [UserTask]
    host = "http://47.104.157.210:7080/"
    min_wait = 2000
    max_wait = 3000
    user_data = []
    with open('./user.csv', 'r') as f:
        for line in f.readlines():
            user_data.append(line.strip())

if __name__ == '__main__':
    import os
    os.system("locust -f miaosha.py")
参数化方式二:queue队列,并发唯一数据,登录多少请求就并发多少用户数
from locust import HttpUser
from locust import TaskSet
from locust import task
import hashlib
import queue

class UserTask(TaskSet):
    """用户任务类,用户的行为"""

    def on_start(self):
        payload = {
            "times": 1
        }
        res = self.client.post(url="/miaosha/reset3", data=payload)
        if res.json()["msg"] == "成功":
            print("商品重置成功")
        else:
            print("商品重置失败")

    def md5_pwd(self, pwd):
        md5 = hashlib.md5()
        md5.update(b"zr" + pwd.encode("utf8") + b"hg")
        return md5.hexdigest()

    @task
    def test_login(self):
        try:
            data = self.user.user_data_queue.get()      # get从队列里取值数据
        except queue.Empty:
            print("用户数据取完了")
            exit(0)  # 当队列里面的数据取完了,队列取完数据就停止运行,账号取值完后就停止运行   exit进行退出,正常退出

        payload = {
            "mobile": data,
            "password": self.md5_pwd("111111")
        }
        print(payload)
        with self.client.post(url="/login/login", data=payload, name="登录", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                self.token = res.json()['data']
                res.success()  # 断言成功
            else:
                res.failure("登录失败")

    @task
    def test_get_goods_list(self):
        """获取商品列表请求"""
        with self.client.get(url="/goods/Jlist", name="获取商品列表", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("获取商品列表失败")

    @task
    def test_goods_detail(self):
        """获取商品详情"""
        with self.client.get(url=f"/goods/detail/1", name="获取商品详情", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("获取商品详情失败")

    @task
    def test_miaosha(self):
        """秒杀接口"""
        payload = {
            "goodsId": 1,
            "token": self.token
        }
        with self.client.post(url="/miaosha/miaosha", data=payload, name="秒杀接口", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("秒杀商品失败")

class WebSitUser(HttpUser):
    """定义用户类,访问用户"""
    tasks = [UserTask]
    host = "http://47.104.157.210:7080/"
    min_wait = 2000
    max_wait = 3000
    user_data_queue = queue.Queue()
    for one in range(13588000000, 13588000999):
        user_data_queue.put(str(one))

if __name__ == '__main__':
    import os
    os.system("locust -f miaosha.py")
参数化方式三:和上面的方式二类似,队列循环,可以去做一个判断,队列queue为空的话再往队列里加数据即可

15:locust集合点  from gevent._semaphore import Semaphore  需要导入 Semaphore这个类

from locust import HttpUser
from locust import TaskSet
from locust import task
from locust import events
import hashlib
import queue
from gevent._semaphore import Semaphore

all_locust_spawned = Semaphore()  # Semaphore: 信号量
all_locust_spawned.acquire()  # 信号量实例上锁

def on_hatch_complete(**kwargs):
    all_locust_spawned.release()  # 创建钩子函数

# 挂载钩子函数    为了让所有的locust示例产生完成后触发,进行钩子函数的监听状态
events.spawning_complete.add_listener(on_hatch_complete)

class UserTask(TaskSet):
    """用户任务类,用户的行为"""

    def on_start(self):
        payload = {
            "times": 1
        }
        res = self.client.post(url="/miaosha/reset3", data=payload)
        if res.json()["msg"] == "成功":
            print("商品重置成功")
        else:
            print("商品重置失败")

    def md5_pwd(self, pwd):
        md5 = hashlib.md5()
        md5.update(b"zr" + pwd.encode("utf8") + b"hg")
        return md5.hexdigest()

    @task
    def test_login(self):
        try:
            data = self.user.user_data_queue.get()  # get从队列里取值数据
        except queue.Empty:
            print("用户数据取完了")
            exit(0)  # 当队列里面的数据取完了,队列取完数据就停止运行,账号取值完后就停止运行   exit进行退出,正常退出

        payload = {
            "mobile": data,
            "password": self.md5_pwd("111111")
        }
        print(payload)
        with self.client.post(url="/login/login", data=payload, name="登录", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                self.token = res.json()['data']
                res.success()  # 断言成功
            else:
                res.failure("登录失败")

    @task
    def test_get_goods_list(self):
        """获取商品列表请求"""
        with self.client.get(url="/goods/Jlist", name="获取商品列表", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("获取商品列表失败")

    @task
    def test_goods_detail(self):
        """获取商品详情"""
        with self.client.get(url=f"/goods/detail/1", name="获取商品详情", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("获取商品详情失败")

    @task
    def test_miaosha(self):
        """秒杀接口"""
        payload = {
            "goodsId": 1,
            "token": self.token
        }
        all_locust_spawned.wait()       # 限制在所有用户准备完成处于等待状态,实现集合点,类似jmeter同步定时器
        # 在这里wait等待,等待所有的用例加载完成以后然后去释放请求秒杀接口
        with self.client.post(url="/miaosha/miaosha", data=payload, name="秒杀接口", catch_response=True) as res:
            if res.status_code == 200 and res.json()["msg"] == "成功":
                res.success()
            else:
                res.failure("秒杀商品失败")

class WebSitUser(HttpUser):
    """定义用户类,访问用户"""
    tasks = [UserTask]
    host = "http://47.104.157.210:7080/"
    min_wait = 2000
    max_wait = 3000
    user_data_queue = queue.Queue()
    for one in range(13588000000, 13588000999):
        user_data_queue.put(str(one))

if __name__ == '__main__':
    import os

    os.system("locust -f miaosha.py")

集合点:
  通过locust得基于gevent并发得机制,引入gevent的锁的概念,代入到locust的钩子函数中,实现集合点统一并发概念

 16:locust深入学习

  locust模拟tcp客户端进行socket并发 参考文档:  https://www.cnblogs.com/jumping0709/p/15687877.html

  Locust 2.8 使用文档 - 基础篇 参考文档:    https://www.csdn.net/tags/MtTaAgwsMzY4OTg3LWJsb2cO0O0O.html

  LocustIO官方文档:  https://blog.csdn.net/swinfans/article/details/88915176

  Locust任务等待机制:  https://blog.csdn.net/Orangesir/article/details/114914969      constantbetweenconstant_pacing

19:locust模拟tcp客户端进行socket并发  简单模板:一

  构造一个 TcpSocketUser继承自User,并且将一个实现了socket方法的实例化对象赋值给TcpSocketUser.client方法即可  简单实现,基础版本如下

locustfile.py
import socket from locust import User, TaskSet, task import time class TcpSocketUser(User): abstract = True client: socket.socket = None def __init__(self, *args, **kwargs): super(TcpSocketUser, self).__init__(*args, **kwargs) self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host, port = self.host.split(":") self.client.connect((host, int(port))) class TestTaskSet(TaskSet): @task(1) def send_data(self): print("执行一次") start_time = time.time() while True: if time.time() - start_time > 10: break self.client.send(b"This is send data!") data = self.client.recv(1024) print(data) time.sleep(3) class TcpTestUser(TcpSocketUser): tasks = [TestTaskSet] host = "localhost:8001" if __name__ == "__main__": import os os.system("locust")
server.py

import gevent
from gevent import socket, monkey
monkey.patch_all()

def server(port):
    try:
        s = socket.socket()
        s.bind(('localhost', port))
        s.listen(500)
        while True:
            cli, addr = s.accept()
            gevent.spawn(handle_request, cli)
    except KeyboardInterrupt as e:
        print(e)

def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            if not data:
                conn.close()
            else:
                print("recv:", data)
                conn.send(b"Received!")
    except OSError as e:
        print("client has been closed")

    except Exception as ex:
        print(ex)
    finally:
        conn.close()

if __name__ == '__main__':
    server(8001)

gevent异步实现server端接受请求和收发逻辑
以上的代码实现了功能,客户端定时向服务端发送数据,服务端收到后进行回复,运行后发现可行,此时初步的并发框架已经完成。

 20:locust模拟tcp客户端进行socket并发  简单模板:二

需求:
    客户端每隔5s同时向服务器发送两条数据 “This is First data!” 和 “This is Second data!”,但是服务端因为要处理的原因,只能20s后才能返回结果,
# locustfile.py

import socket
from locust import User, TaskSet, task, constant

class TcpSocketUser(User):
    abstract = True
    client: socket.socket = None

    def __init__(self, *args, **kwargs):
        super(TcpSocketUser, self).__init__(*args, **kwargs)
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        host, port = self.host.split(":")
        self.client.connect((host, int(port)))

class TestTaskSet(TaskSet):
    @task(1)
    def send_data(self):
        send_data = b"This is First data!"
        self.client.send(send_data)
        print(send_data)
        send_data = b"This is Second data!"
        self.client.send(send_data)
        print(send_data)
        recv_data = self.client.recv(1024)
        print(recv_data)

class TcpTestUser(TcpSocketUser):
    wait_time = constant(5)         # 任务之间间隔时间5s
    tasks = [TestTaskSet]
    host = "localhost:8001"

if __name__ == "__main__":
    import os
    os.system("locust")
server.py

from gevent import monkey; monkey.patch_all()
import gevent
from gevent import socket
import time

def server(port):
    try:
        s = socket.socket()
        s.bind(('localhost', port))
        s.listen(500)
        while True:
            cli, addr = s.accept()
            print(addr)
            gevent.spawn(handle_request, cli)
    except KeyboardInterrupt as e:
        print(e)

def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            if not data:
                conn.close()
            else:
                print("recv:", data)
                time.sleep(20)
                if b"First" in data:
                    conn.send(b"First Received!")
                elif b'Second' in data:
                    conn.send(b"Second Received!")
    except OSError as e:
        print("client has been closed")

    except Exception as ex:
        print(ex)
    finally:
        conn.close()

if __name__ == '__main__':
    server(8001)
运行起来会发现,当客户端数据发送完成之后,只能等待20s,
  收到服务端的答复后才能返回开始下一次数据发送,这是因为recv默认是个阻塞方法,当没有收到数据时会一直阻塞在此,
  影响下一次任务的开始,此时我们要解决阻塞的问题,可以使用recv的block=False,但官方文档在Testing non-HTTP systems 章节提到,协议库要用可以猴子补丁的。

  20:locust模拟tcp客户端进行socket并发  非阻塞代码模板,使用gevent协程处理recv收的逻辑

locustfile.py

from gevent import socket, monkey;monkey.patch_all()
import gevent
import socket
from locust import User, TaskSet, task, constant
import time

class TcpSocketUser(User):
    abstract = True
    client: socket.socket = None

    def __init__(self, *args, **kwargs):
        super(TcpSocketUser, self).__init__(*args, **kwargs)
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        host, port = self.host.split(":")
        self.client.connect((host, int(port)))
        gevent.spawn(handle_recv, self.client)

def handle_recv(conn):
    while True:
        data = conn.recv(1024)
        if not data:
            conn.close()
        else:
            print(data)
        time.sleep(0.1)

class TestTaskSet(TaskSet):
    @task(1)
    def send_data(self):
        send_data = b"This is First data!"
        self.client.send(send_data)
        print(send_data)
        send_data = b"This is Second data!"
        self.client.send(send_data)

class TcpTestUser(TcpSocketUser):
    wait_time = constant(5)         # 任务之间间隔时间5s
    tasks = [TestTaskSet]
    host = "localhost:8001"

if __name__ == "__main__":
    import os
    os.system("locust")
server.py
from gevent import monkey; monkey.patch_all() import gevent from gevent import socket import time def server(port): try: s = socket.socket() s.bind(('localhost', port)) s.listen(500) while True: cli, addr = s.accept() print(addr) gevent.spawn(handle_request, cli) except KeyboardInterrupt as e: print(e) def handle_request(conn): try: while True: data = conn.recv(1024) if not data: conn.close() else: print("recv:", data) time.sleep(20) for i in data.split(b'!'): if b"First" in data: conn.send(b"First Received!") elif b'Second' in data: conn.send(b"Second Received!") except OSError as e: print("client has been closed") except Exception as ex: print(ex) finally: conn.close() if __name__ == '__main__': server(8001)
以上代码可实现上述第二个需求,即客户端每隔5s同时向服务器发送两条数据 “This is First data!” 和 “This is Second data!”,
  但是服务端因为要处理的原因,只能20s后才能返回结果
至此,已经可以实现并发了。

21:locust模拟tcp客户端进行socket并发:并发结果注册到测试报告

HttpUser源码中:
    HttpSession实例化时传入三个参数,self.host,self.environment.events.request,self,
  跟报告相关的主要是第二个参数,我们到HttpSession中进行查看。

ResponseContextManager 类中就含有将结果注册到测试报告的相关代码
注册到报告中,如何确认是成功或失败呢?
  exception用法,该参数传入的是方法执行错误产生时所抛出的错误实例,如果方法执行成功则填为None,
locustfile.py

from
gevent import socket, event, monkey; monkey.patch_all() import gevent import socket from locust import User, TaskSet, task, constant import time class TcpSocketClient(socket.socket): def __init__(self, af_inet, socket_type, request_event, user): super(TcpSocketClient, self).__init__(af_inet, socket_type) self.connect_event = event.Event() self.request_event = request_event self.user = user def connect(self, addr): start_time = time.time() try: super(TcpSocketClient, self).connect(addr) except Exception as e: total_time = int((time.time() - start_time) * 1000) self.request_event.fire(request_type="tcp", name="connect", response_time=total_time, response_length=0, response=None, context=None, exception=e) else: # 如果没有捕获到错误说明连接成功了,那么set()解除其他任务的阻塞 self.connect_event.set() total_time = int((time.time() - start_time) * 1000) self.request_event.fire(request_type="tcp", name="connect", response_time=total_time, response_length=0, response=None, context=None, exception=None) def send(self, data, flag=False): start_time = time.time() try: self.connect_event.wait() super(TcpSocketClient, self).send(data) total_time = int((time.time() - start_time) * 1000) print(data) self.request_event.fire(request_type="tcp", name="send", response_time=total_time, response_length=len(data), response=None, context=None, exception=None) except OSError as e: total_time = int((time.time() - start_time) * 1000) self.request_event.fire(request_type="tcp", name="send", response_time=total_time, response_length=0, response=None, context=None, exception=e) def recv(self, bufsize, flag=False): start_time = time.time() try: while True: start_time = time.time() self.connect_event.wait() data = super(TcpSocketClient, self).recv(bufsize) total_time = int((time.time() - start_time) * 1000) print(data) self.request_event.fire(request_type="tcp", name="recv", response_time=total_time, response_length=len(data), response=None, context=None, exception=None) except OSError as e: total_time = int((time.time() - start_time) * 1000) self.request_event.fire(request_type="tcp", name="recv", response_time=total_time, response_length=0, response=None, context=None, exception=e) class TcpSocketUser(User): abstract = True client: socket.socket = None def __init__(self, *args, **kwargs): super(TcpSocketUser, self).__init__(*args, **kwargs) self.client = TcpSocketClient(socket.AF_INET, socket.SOCK_STREAM, self.environment.events.request, self) host, port = self.host.split(":") self.client.connect((host, int(port))) gevent.spawn(self.client.recv, 1024) class TestTaskSet(TaskSet): @task(1) def send_data(self): send_data = b"This is First data!" self.client.send(send_data) print(send_data) send_data = b"This is Second data!" self.client.send(send_data) class TcpTestUser(TcpSocketUser): wait_time = constant(5) # 任务之间间隔时间5s tasks = [TestTaskSet] host = "localhost:8001" if __name__ == "__main__": import os os.system("locust")
from gevent import monkey; monkey.patch_all()
import gevent
from gevent import socket
import time

def server(port):
    try:
        s = socket.socket()
        s.bind(('localhost', port))
        s.listen(500)
        while True:
            cli, addr = s.accept()
            print(addr)
            gevent.spawn(handle_request, cli)
    except KeyboardInterrupt as e:
        print(e)

def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            if not data:
                conn.close()
            else:
                print("recv:", data)
                time.sleep(20)
                for i in data.split(b'!'):
                    if b"First" in data:
                        conn.send(b"First Received!")
                    elif b'Second' in data:
                        conn.send(b"Second Received!")
    except OSError as e:
        print("client has been closed")

    except Exception as ex:
        print(ex)
    finally:
        conn.close()

if __name__ == '__main__':
    server(8001)

22:locust模拟tcp客户端进行socket并发:业务融合,写一些和实际业务逻辑相关的

上面21并发结果注册到测试报告还有一个问题:
  注册到报告中的信息,更多是想关注业务的成功与失败,而不是底层的connect、send、recv这些,那该怎么办呢?
  因为我们要模拟的是终端客户向平台发送数据,所以我们新建一个Device类,该类继承自TcpSocketClient,并且会扩展实际的业务。

一个简单需求:
  客户端连接平台后要先发送一条“Register!”指令向平台注册,如果平台回复“Success”,
  则客户端开始以5s的间隔持续向平台发送“Heart!”,若平台超过5s没有响应,则客户端注册失败,不再继续发送信息。
  此时我们更多的关注在于业务的响应时间,比如register和heart的回复时间。
locustfile.py

from gevent import socket, event, monkey; monkey.patch_all()
import gevent
import socket
from locust import User, TaskSet, task, constant
import time
import queue

class TcpSocketClient(socket.socket):
    def __init__(self, af_inet, socket_type, request_event, user):
        super(TcpSocketClient, self).__init__(af_inet, socket_type)
        self.connect_event = event.Event()
        self.request_event = request_event
        self.user = user
        self.recv_queue = queue.Queue()  # 创建一个队列

    def connect(self, addr):
        start_time = time.time()
        try:
            super(TcpSocketClient, self).connect(addr)
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            self.request_event.fire(request_type="tcp",
                                    name="connect",
                                    response_time=total_time,
                                    response_length=0,
                                    response=None,
                                    context=None,
                                    exception=e)
        else:
            # 如果没有捕获到错误说明连接成功了,那么set()解除其他任务的阻塞
            self.connect_event.set()
            total_time = int((time.time() - start_time) * 1000)
            self.request_event.fire(request_type="tcp",
                                    name="connect",
                                    response_time=total_time,
                                    response_length=0,
                                    response=None,
                                    context=None,
                                    exception=None)

    def send(self, data, flag=False):
        start_time = time.time()
        try:
            self.connect_event.wait()
            super(TcpSocketClient, self).send(data)
            total_time = int((time.time() - start_time) * 1000)
            self.request_event.fire(request_type="tcp",
                                    name="send",
                                    response_time=total_time,
                                    response_length=len(data),
                                    response=None,
                                    context=None,
                                    exception=None)
        except OSError as e:
            total_time = int((time.time() - start_time) * 1000)
            self.request_event.fire(request_type="tcp",
                                    name="send",
                                    response_time=total_time,
                                    response_length=0,
                                    response=None,
                                    context=None, exception=e)

    def recv(self, bufsize, flag=False):
        start_time = time.time()
        try:
            while True:
                start_time = time.time()
                self.connect_event.wait()
                data = super(TcpSocketClient, self).recv(bufsize)
                total_time = int((time.time() - start_time) * 1000)
                self.recv_queue.put_nowait(data)  # 接收到的数据塞入队列
                self.request_event.fire(request_type="tcp",
                                        name="recv",
                                        response_time=total_time,
                                        response_length=len(data),
                                        response=None,
                                        context=None,
                                        exception=None)
                time.sleep(0.1)
        except OSError as e:
            total_time = int((time.time() - start_time) * 1000)
            self.request_event.fire(request_type="tcp",
                                    name="recv",
                                    response_time=total_time,
                                    response_length=0,
                                    response=None,
                                    context=None,
                                    exception=e)

class Device(TcpSocketClient):
    def __init__(self, af_inet, socket_type, request_event, user):
        super(Device, self).__init__(af_inet, socket_type, request_event, user)
        self.is_registered = False
        self.heart_dict = {}

    def parse_data(self):
        while True:
            if not self.recv_queue.empty():
                data = self.recv_queue.get_nowait()
                print(self.getsockname(), data)
                if b"Success" in data:
                    self.is_registered = True
                elif b"Heart Reply" in data:
                    start_time = self.heart_dict.get("Heart")
                    total_time = int((time.time() - start_time) * 1000)
                    self.request_event.fire(request_type="tcp",
                                            name="Heart",
                                            response_time=total_time,
                                            response_length=0,
                                            response=None,
                                            context=None,
                                            exception=None)
            time.sleep(0.1)

    def register(self):
        self.send(b"Register!")

    def heart(self):
        self.send(b"Heart!")

class TcpSocketUser(User):
    abstract = True
    client: socket.socket = None

    def __init__(self, *args, **kwargs):
        super(TcpSocketUser, self).__init__(*args, **kwargs)
        self.client = Device(socket.AF_INET, socket.SOCK_STREAM, self.environment.events.request, self)
        host, port = self.host.split(":")
        self.client.connect((host, int(port)))
        gevent.spawn(self.client.recv, 1024)  # 协程  循环接受消息,然后放入队列
        gevent.spawn(self.client.parse_data)  # 协程  循环从队列拿消息然后做判断

class TestTaskSet(TaskSet):
    def on_start(self):
        start_time = time.time()
        try:
            self.client.register()
            count = 0
            while not self.client.is_registered:
                # is_registered默认False, False没有注册一直死循环,100s还是false的话说明注册超时了,那么直接raise报错
                # 如果is_registered变成了True说明注册成功了,走下面的逻辑,注册不报错一般都走else逻辑
                time.sleep(0.1)
                count += 1
                if count == 100:
                    # exit(0)   如果登录失败,退出脚本得了
                    raise TimeoutError("注册失败")
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            self.client.request_event.fire(request_type="tcp",
                                           name="register",
                                           response_time=total_time,
                                           response_length=0,
                                           response=None,
                                           context=None,
                                           exception=e)
        else:
            total_time = int((time.time() - start_time) * 1000)
            self.client.request_event.fire(request_type="tcp",
                                           name="register",
                                           response_time=total_time,
                                           response_length=0,
                                           response=None,
                                           context=None,
                                           exception=None)

    @task(1)
    def heart(self):
        if self.client.is_registered:
            self.client.heart()     # 如果处于注册状态,那么就调用heart函数,一直发送"heart"
            self.client.heart_dict["Heart"] = time.time()   # 请求时间塞入列表里

class TcpTestUser(TcpSocketUser):
    wait_time = constant(5)  # 任务之间间隔时间5s
    tasks = [TestTaskSet]
    host = "localhost:8001"

if __name__ == "__main__":
    import os
    os.system("locust")
client.py

from gevent import monkey; monkey.patch_all()
import gevent
from gevent import socket
import time

def server(port):
    try:
        s = socket.socket()
        s.bind(('localhost', port))
        s.listen(500)
        while True:
            cli, addr = s.accept()
            print(addr)
            gevent.spawn(handle_request, cli)
    except KeyboardInterrupt as e:
        print(e)

def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            if not data:
                conn.close()
            else:
                print("recv:", data)
                for i in data.split(b'!'):
                    if b"Register" in data:
                        conn.send(b"Success!")    # 让对方收不到Success,对方收到后登录状态就是True了就会运行逻Heart了
                        pass
                    if b"Heart" in i:
                        conn.send(b"Heart Reply!")
    except OSError as e:
        print("client has been closed")

    except Exception as ex:
        print(ex)
    finally:
        conn.close()

if __name__ == '__main__':
    server(8001)

 23:locust分布式压测

分布式场景:
Locust分布式场景有两种:
  1.单台机器设置Master和Slave
  2.多台机器时,一台调度机(Master),其他机器设置执行机(Slave)
前提:  主机从机都安装python环境和locust库,主从机都有python的执行文件,请求的代码
  1、主机(master)装好locust环境
  2、从机(slave)装好locust环境
  3、主机/从机上都要有执行的Python文件(你自己写的压测脚本)
分布式运行原理:
  master节点不产生压力  控制机
  多个slave节点,执行机,压力机,
  master节点分发给每个压力机执行里面的脚本信息

locust分布式执行步骤:

master节点控制机:

  1:master节点控制机命令行输入:locust -f 文件 --master
    locust -f miaosha.py --master
  2:输入:http://localhost:8089/  登录locust的web页面
  3:假设输入5个并发去跑,这时候暂时什么都不会运行,web界面多了了新的选择字段:Wrokers
    现在这是主节点,不是工作节点,所以这个节点不会产生压力,需要分配slave从节点
  4:

salve节点执行机:

  1:机器命令行打开输入:loucst -f 文件 --worker --master-host=ip地址
    locust -f miaosha.py --worker    没有输入host地址默认本机
    输入这个后,宿主机就能够和salve机通信,宿主机的web端页面的worker能找到这个执行机并且处于ready准备状态
    这世界就可以在master控制机输入并发数执行脚本
    需要加多个节点按照上面的命令在执行机输入命令执行locust就行
headless:无网页模式启动, -c是设置并发用户数,-r是设置每秒进入用户数,-t设置运行时长  命令行模式
locust -f python文件 --headless -u 100 -r 10

no_web形式启动locust:
如果采用no_web形式,则需使用--headless`参数,并会用到如下几个参数。
  -u, --users:指定并发用户数;
  -r, --spawn-rate:指定并发加压速率,默认值位1。

web形式启动locust:
  如果采用web形式,,则通常情况下无需指定其它额外参数,Locust默认采用8089端口启动web;
  如果要使用其它端口,就可以使用如下参数进行指定。
    -P, --port:指定web端口,默认为8089.
    终端中--->进入到代码目录: locust -f locustfile.py --host = xxxxx.com
    -f 指定性能测试脚本文件
    -host 被测试应用的URL地址【如果不填写,读取继承(HttpLocust)类中定义的host】
  如果Locust运行在本机,在浏览器中访问http://localhost:8089即可进入Locust的Web管理页面;如果Locust运行在其它机器上,那么在浏览器中访问http://locust_machine_ip:8089

 24:其他文档

1:locust测试数据的处理  https://blog.csdn.net/qq_34979346/article/details/85019362
2:性能测试工具--Locust官方文档(API)解读(全)  https://cloud.tencent.com/developer/article/1594240

 25:locust压测实例HTTP接口

# 封装基础库,appuser类
import json
import requests
class AppUser: def __init__(self, gateway, account, password): self.gateway = gateway self.account = account self.password = password self.headers = None self.in_id = [] self.restoreId = [] def app_login(self):
     # 这里登录的同时创建header头,把鉴权信息放头里,后面的接口可以直接使用self.headers
return r.json() def scanDeviceType(self, content): """二维码扫码识别"""return res.json() def chargeConfirm(self, gunId): """扫码启动充电前确认信息"""return res.json()
  """后面封装更多的app用户的操作逻辑"""
# 封装locust脚本
from gevent import monkey;monkey.patch_all()
import queue
import time

from functools import wraps
from locust import task
from locust import SequentialTaskSet
from locust import events
from locust import User
from gevent._semaphore import Semaphore
from appUserApi import AppUser  # 调用上面封装了的AppUser类
from logDir import logger

all_locust_spawned = Semaphore()
all_locust_spawned.acquire()

def on_hatch_complete(**kwargs):
    all_locust_spawned.release()

events.spawning_complete.add_listener(on_hatch_complete)

# 日志装饰器,把task任务函数注入到locust报告里,函数执行的时间,返回数据的字节数和一些异常信息注入locust到报告
def reporter(api_name):
    def bar(func):
        @wraps(func)
        def inner(*args, **kwargs):
            start_time = time.time()
            res, self = func(*args, **kwargs)
            logger.info(f"{func.__name__}:{res}")
            total_time = int((time.time() - start_time) * 1000)
            if res["code"] == 200:
                self.user.environment.events.request.fire(request_type="HTTP",
                                                          name=api_name,
                                                          response_time=total_time,
                                                          response_length=len(str(res).encode("utf8")),
                                                          response=None,
                                                          context=None,
                                                          exception=None)
            else:
                self.user.environment.events.request.fire(request_type="HTTP",
                                                          name=api_name,
                                                          response_time=total_time,
                                                          response_length=len(str(res).encode("utf8")),
                                                          response=None,
                                                          context=None,
                                                          exception=res["error"])
        return inner
    return bar

class MyTaskSet(SequentialTaskSet):
    def on_start(self):
        print("初始化用户")
        self.app_user = self.user.app_user_queue.get()
        self.app_user.app_login()  # 用户登录
    @reporter("家桩详情")
    @task
    def pile_personal(self):
        try:
            res = self.app_user.stationDetail()
            return res, self
        except Exception as e:
            print(e)
            return {"code": 500, "error": "请求错误"}, self
    def on_stop(self):
        print("执行完后的步骤")
class WebSitUser(User):
    host = "xxx"  # 指定主机地址
    app_user_list = [f"1888888{i:0>4}" for i in range(1, 2000)]
    login_password = "xxx"
    tasks = [MyTaskSet]
    min_wait = 100
    max_wait = 200
    app_user_queue = queue.Queue()
    for app_user_account in app_user_list:
        app_user_queue.put(AppUser(host, app_user_account, login_password))
if __name__ == '__main__':
    import os
    import sys

    current_dir = os.getcwd()
    sys.path.insert(0, current_dir)
    os.environ['NO_PROXY'] = 'stackoverflow.com'
    os.system("locust -f APP充电站详情.py")

 25:locust压测实例Websocket接口

from gevent import event, monkey;monkey.patch_all()
import gevent
import json
from datetime import datetime, timedelta
import time
import queue
import hashlib
import base64
import logging
import random

import websocket
from locust import User, TaskSet, task, constant
import requests

BASIC_SALT = "" # 加密的鉴权码密钥,用于websocket连接服务器的

firmwareVersion = {}
logType = []

def get_logger(log_dir):
    logger = logging.getLogger()
    fh = logging.FileHandler(log_dir, encoding='utf-8')
    ch = logging.StreamHandler()
    logging.root.setLevel(logging.NOTSET)
    fh.setLevel(logging.DEBUG)
    ch.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    logger.addHandler(fh)
    logger.addHandler(ch)
    return logger
logger_boj = get_logger(f"./locustPile.log")


class EneGatewayHost:
    china_dev = "enedev.cn"  # 中国开发
    china_test = "enetest.cn"  # 中国测试
    china_prod = "eneprod.cn"  # 中国生产
    England_test = "enetestuk.com"  # 英国测试
    USA_test = "enetestus.com"  # 美西测试
    USA_prod = "eneprodus.com"  # 美国生产
    England_prod = "eneproduk.com"  # 英国生产
    Europe_prod = "eneprodeu.com"  # 欧洲生产
    Canada_prod = "eneprodca.com"  # 加拿大生产
    ene_wb_dev_cn = "charge-dev.cn"  # 联调环境

class PlatFormHost:
    china_test = "https://enetest.cn"  # 中国测试
    England_test = "https:/enetestuk.com"  # 英国测试

class Message:
    Heartbeat = ["Heartbeat", {}]  # 心跳消息

  # 定义发送给服务端的消息数据,自己根据需求补充
def getUTC0(): """返回:2022-07-06T00:36:39.000Z""" from datetime import datetime, timedelta now_time = datetime.now() utc_time = now_time - timedelta(hours=8) # UTC只是比北京时间提前了8个小时 utc_time = utc_time.strftime("%Y-%m-%dT%H:%M:%S" + ".000Z") return utc_time def get_utc0_time(): """获取UTC0时区时间,返回:20220706003708""" utc_time = datetime.now() - timedelta(hours=8) utc_time = utc_time.strftime("%Y%m%d%H%M%S") return utc_time def calc_pile_Authorization(ctrlSn, pwd, mac): sha256 = hashlib.sha256() sha256.update(f"{ctrlSn}:{pwd}:{BASIC_SALT}:{mac.replace('-', ':').lower()}".encode('utf-8')) in_data = sha256.digest() res = 'Authorization:Basic ' + str(base64.b64encode((ctrlSn + ":" + base64.b64encode(in_data).decode("utf-8")). encode('utf-8')).decode('utf-8')) return res def get_connect_info(ene, pile_info): """ 获取连接信息,返回连接的url和header :param ene: 环境(china_dev,china_test,china_prod,England_test,USA_test,USA_prod,England_prod,Europe_prod) :param pile_info: 桩信息 :return: """ url = f"ws://{getattr(EneGatewayHost, ene)}/ws/websocket?sn={pile_info[0]}" header = [pile_info[1], "xxx", "xxx"] return url, header def generate_msg(msg_type, requests_type, message_flag, pile_connectorId=1, idTag=None, transactionId=None, change_voltage=220, change_electric=32, change_value=None, stop_charging_id=None, Power_Phase=1, gunNO='1', lastTime=None, pileSN=None, seq=None, eventType=1, securityHappen=1, card_id=None, soc_value=None): out_data = [] msg = getattr(Message, msg_type) for data in msg: out_data.append(data)return out_data
  # 生成最终发送的消息,消息封装,自定义所有的发送消息
class MyPile: def __init__(self, ene, in_pile, locustUser): self.reportObj = locustUser.user.environment.events.request.fire # 注入报告的 self.connect_event = event.Event() # 信号量,connect连接成功后才解开锁 self.connect_flag = False self.message_time_dic = {} # 消息收到的时间记录 self.card_value = 0 self.start_time = time.time() self.bootAccept = False # BOOT准备消息,发送boot后需要recv到Accepted才会运行后面的逻辑 self.Power_Phase = 3 # 电源相数默认3相 self.card_Charging_flag = False # 默认没有刷卡充电,刷卡充电的标签 self.time_flag = 1 self.requests_type2 = 2 # 请求数据类型 self.requests_type3 = 3 # 请求数据类型 self.idTag_queue = queue.Queue() # 充电id self.stop_charging_id_queue = queue.Queue() # 停止充电的id标志 self.change_flag = False # 充电状态标志,默认没有充电 self.transactionId_queue = queue.Queue() # 交易id self.change_electric = 32 # 充电电流 self.change_voltage = 220 # 充电电压 self.change_energy = int(self.change_electric) * int(self.change_voltage) * int(self.Power_Phase) self.pile_state = "heartbeat" # 默认桩状态正常 self.sn = in_pile['pileSn'] self.csn = in_pile['ctrlSn'] self.password = in_pile['password'] self.mac = in_pile['mac'] self.au = calc_pile_Authorization(self.csn, self.password, self.mac) self.url, self.header = get_connect_info(ene, (self.sn, self.au)) self.ene = ene self.ws = websocket.WebSocket() def connect_server(self): """连接服务器""" while not self.connect_flag: start_time = time.time() try: self.ws.connect(self.url, header=self.header) except Exception as e: # 捕获到错误说明连接失败了,间隔1s后继续连接 total_time = int((time.time() - start_time) * 1000) self.escalationReporting(total_time, "Connect", exception=e) # 连接失败了日志上报 time.sleep(1) else: self.connect_event.set() # 如果没有捕获到错误说明连接成功了,那么set()解除其他任务的阻塞 self.connect_flag = True # 连接成功那么循环结束 total_time = int((time.time() - start_time) * 1000) self.escalationReporting(total_time, "Connect") # 连接成功了日志上报 gevent.spawn(self.while_recv) self.BootNotification() # websocket鉴权ok后需要Boot认证,发送boot后收到Accepted才启动充电桩,不然断连 self.send_message("TARIFF_RULE_1", 2, pileSN=self.sn) time.sleep(1) self.send_message("TARIFF_RULE_2", 2, pileSN=self.sn) self.pile_charging_Preparing() # 插枪准备充电状态 gevent.spawn(self.while_heartbeat) # gevent.spawn(self.while_frontLog) def BootNotification(self): while 1: for j in range(3): self.send_message("BootNotification", self.requests_type2) time.sleep(0.1) if self.bootAccept: break else: time.sleep(10) if self.bootAccept: break else: print("Boot连接失败,断开连接后继续重试") self.ws.close() self.ws = websocket.WebSocket() self.ws.connect(self.url, header=self.header) self.BootNotification() def pile_charging_Preparing(self): """模拟桩插枪""" self.send_message("Available", self.requests_type2) self.pile_state = "Available" time.sleep(0.1) self.send_message("Preparing", self.requests_type2) self.pile_state = "Preparing" def change_pile_state(self, state): """切换桩的状态""" self.send_message(state, self.requests_type2) self.pile_state = state def frontLog_acceptEventLog(self, in_id="localPrintf"): """模拟桩接口上报日志""" in_data = random.choice(logType) url = f"" start_time = time.time() try: reps = requests.post(url).json() print(reps) result_time = int((time.time()-start_time)*1000) if reps['message'] == 'OK': self.escalationReporting(result_time, "frontLog", request_type="HTTP", response_length=len(json.dumps(reps).encode("utf-8"))) else: self.escalationReporting(result_time, "frontLog", request_type="HTTP", response_length=len(json.dumps(reps).encode("utf-8")), exception=reps['message']) except Exception as e: self.escalationReporting(result_time, "frontLog", request_type="HTTP", response_length=len(json.dumps(reps).encode("utf-8")), exception=e) def while_frontLog(self): time.sleep(5) self.frontLog_acceptEventLog() time.sleep(10) def escalationReporting(self, total_time, name, response_length=0, request_type="WebSocket", exception=None): """日志上报""" self.reportObj(request_type=request_type, name=name, response_time=total_time, response_length=response_length, response=None, context=None, exception=exception) def changer_ing(self): value = 0 idTag = self.idTag_queue.get() self.send_message("Accepted", self.requests_type3, idTag=idTag) time.sleep(0.1) self.send_message("Authorize", self.requests_type2, idTag=idTag) time.sleep(0.1) self.send_message("Charging", self.requests_type2, pile_connectorId=1) # 枪1发送充电中的消息 time.sleep(0.1) self.send_message("StartTransaction", self.requests_type2, idTag=idTag) # print("开启充电结束了,循环发送:meterValue") transactionId = self.transactionId_queue.get() while 1: try: if not self.change_flag: stop_charging_id = self.stop_charging_id_queue.get() # print("充电结束了,调用充电逻辑") self.send_message("Rejected", self.requests_type3) time.sleep(0.1) self.send_message("Accepted", self.requests_type3, stop_charging_id=stop_charging_id) time.sleep(0.1) self.send_message("Finishing", self.requests_type2) time.sleep(0.1) self.send_message("Preparing", self.requests_type2) time.sleep(0.1) self.send_message("StopTransaction", self.requests_type2, idTag=idTag, transactionId=transactionId, change_value=str(value)) # 充电结束后发送结束信息,meterStop本次充电耗费了多少电量 break self.send_message("MeterValues", self.requests_type2, change_value=str(value), transactionId=transactionId, change_voltage=self.change_voltage, change_electric=self.change_electric, Power_Phase=self.Power_Phase) # print(f"MeterValues发送成功:电流{self.change_electric}:电压{self.change_voltage}:电量{str(value)}") time.sleep(5) value += int(round(self.change_energy / 3600 * 5 * self.Power_Phase, 1)) except ConnectionResetError as e: self.ws.connect(self.url, header=self.header) print(f"changer_ing:{e}") def send_message(self, msg_type, requests_type, *args, **kwargs): """发送消息""" message_flag = f"{time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))}{self.sn[-5:-1]}{self.time_flag:0>4}" message = json.dumps(generate_msg(msg_type, requests_type, message_flag, *args, **kwargs)) start_time = time.time() try: self.connect_event.wait() # 没有连接成功阻塞在这里等待连接成功才发送消息 self.ws.send(message) total_time = int((time.time() - start_time) * 1000) self.escalationReporting(total_time, "Send", response_length=len(message.encode("utf8"))) # 上报日志 except OSError as e: total_time = int((time.time() - start_time) * 1000) self.escalationReporting(total_time, "Send", response_length=0, exception=e) # 上报日志 self.message_time_dic[self.sn + message_flag] = [time.time(), msg_type] # 发送消息的时候注入消息时间和标志写入字典 self.time_flag += 1 def send_heartbeat(self): self.send_message("Heartbeat", self.requests_type2) def while_heartbeat(self): while 1: try: self.send_heartbeat() time.sleep(10) except ConnectionResetError as e: print(f"while_heartbeat: {e}") self.ws.connect(self.url, header=self.header) def recv_msg(self): try: recv_message = self.ws.recv() except Exception as e: print(e) recv_message = None time.sleep(10) self.ws.connect(self.url, header=self.header) return recv_message def total_time_bootRecvSend(self, recv_message): """处理收到消息的报告""" recv_message_flag = self.sn + json.loads(recv_message)[1] if recv_message_flag in self.message_time_dic and recv_message: # 如果收到的消息在消息列表里,注入报告 send_message_time = self.message_time_dic[recv_message_flag][0] recv_message_time = time.time() message_time = int((recv_message_time - send_message_time) * 1000) message_name = self.message_time_dic[recv_message_flag][1] del self.message_time_dic[recv_message_flag] self.escalationReporting(message_time, "Recv", response_length=len(recv_message.encode("utf8"))) # 注入报告 self.escalationReporting(message_time, message_name, response_length=len(recv_message.encode("utf8"))) # 注入报告 if message_time <= 1000: self.escalationReporting(message_time, "Recv:<=1000ms", response_length=len(recv_message.encode("utf8"))) # 注入报告 elif message_time <= 3000: self.escalationReporting(message_time, "Recv:<=3000ms", response_length=len(recv_message.encode("utf8"))) # 注入报告 elif message_time <= 5000: self.escalationReporting(message_time, "Recv:<=5000ms", response_length=len(recv_message.encode("utf8"))) # 注入报告 elif message_time <= 20000: self.escalationReporting(message_time, "Recv:<=20000ms", response_length=len(recv_message.encode("utf8"))) # 注入报告 logger_boj.info( f"5s-20s Message:{recv_message_flag}/SendTime:{send_message_time}/RecvTime{recv_message_time}") else: self.escalationReporting(message_time, "Recv:>20000ms", response_length=len(recv_message.encode("utf8"))) # 注入报告 logger_boj.info( f">20s Message:{recv_message_flag}/SendTime:{send_message_time}/RecvTime{recv_message_time}") else: # 如果收到的消息没有在消息列表里,那么随便注入报告,收到消息的时间为0 self.escalationReporting(0, "Recv") def while_recv(self): while 1: try: self.connect_event.wait() # 没有连接成功前阻塞在这里,等待连接成功后做后面收发逻辑 recv_message = self.recv_msg() self.total_time_bootRecvSend(recv_message) # 收到的消息注入报告 except ConnectionResetError as e: print(f"while_recv:{e}") recv_message = "收不到消息,重新连接websocket" # 收不到消息了 self.ws.connect(self.url, header=self.header) if self.bootAccept is False: if recv_message.__contains__("Accepted"): self.bootAccept = True if recv_message.__contains__("RemoteStartTransaction"): # 启动充电 try: self.idTag_queue.put(json.loads(recv_message)[-1]["idTag"]) except Exception as e: print(e, "00000000000000000000000000000000000000000000") self.change_flag = True gevent.spawn(self.changer_ing) elif recv_message.__contains__("TARIFF_RULE"): """如果收到TARIFF_RULE 计费相关消息 获取seq并且 响应accept""" try: message_seq = json.loads(recv_message)[1] self.send_message("Accepted", self.requests_type3, idTag=message_seq) except Exception as e: print(e) elif recv_message.__contains__('"data":{"getConfig":1}'): """如果收到服务端的 getConfig消息,需要响应两个数据""" try: message_seq = json.loads(recv_message)[1] self.send_message("Accepted", self.requests_type3, idTag=message_seq) # 回一个config time.sleep(1) self.send_message("Config", self.requests_type2, pileSN=self.sn) # 回一个config except Exception as e: print(e) elif recv_message.__contains__('{"meterValuePerSec"}'): """如果收到服务端的 meterValuePerSec 响应""" try: message_seq = json.loads(recv_message)[-1]["seq"] self.send_message("Accepted", self.requests_type3, idTag=message_seq) # 回一个config except Exception as e: print(e) elif recv_message.__contains__('MeterValueSampleInterval'): """如果收到服务端的 MeterValueSampleInterval 响应""" try: message_seq = json.loads(recv_message)[1] self.send_message("Accepted", self.requests_type3, idTag=message_seq) # 回一个config except Exception as e: print(e) elif recv_message.__contains__("RemoteStopTransaction"): # 结束充电 self.change_flag = False try: self.stop_charging_id_queue.put(json.loads(recv_message)[1]) except Exception as e: print(e, "1111111111111111111111111111111111111") elif recv_message.__contains__("transactionId") and recv_message.__contains__('"userId":"'): # 获取交易id try: self.transactionId_queue.put(json.loads(recv_message)[2]["transactionId"]) except Exception as e: print(e, recv_message) pass class MyTaskSet(TaskSet): def on_start(self): print("初始化用户") self.pile_data = self.user.ge_pile_data.__next__() self.pileObj = MyPile(self.user.ene, self.pile_data, self) self.pileObj.connect_server() @task def pile_change_status(self): type_lis = ["Preparing", "Available"] while 1: self.pileObj.send_message(random.choice(type_lis), 2) # 切换状态 time.sleep(random.randint(30, 1600)) def on_stop(self): print("执行完后的步骤") def generate_pile(): for index in range(9500, 10001): pile_sn = f'' ctrlSn = f"" in_data = f"" mac = f"" payload = { "bluetoothMac": "", "ctrlSn": ctrlSn, "lacMac": "", "mac": mac, "password": "123456", "pileSn": pile_sn, "pin": "01234567", "productModel": "", "sealerNo": ""} yield payload class WebSitUser(User): host = "England_test" # 指定主机地址 "china_test" , "England_test" ge_pile_data = generate_pile() ene = "England_test" tasks = [MyTaskSet] min_wait = 100 max_wait = 200 if __name__ == "__main__": import os import sys current_dir = os.getcwd() sys.path.insert(0, current_dir) os.environ['NO_PROXY'] = 'stackoverflow.com' os.system("locust -f ywtLocustPile.py")

 23:模拟充电桩源码  模拟充电桩是使用  python + requests + websocket-client + tkinter 实现的

from ac_common import *

class AcmpMessage:
    def action_pushrecord(self, rcType, seq, unitId, data):
        pushrecord = {
            "action": "PushRecord",
            "seq": seq,
            "payload": {
                "msgId": "AL0038B1GN7CTEST01:63733760:174",
                "rcData": [
                    {
                        "unitId": unitId,
                        "rcType": rcType,
                        "rcVer": 0,
                        "data": data
                    }
                ]
            }
        }
        return pushrecord

    def action_upMessage(self, seq, msgId, srcAddr, frameId, optType, data):
        upMessage = {
            "action": "UpMessage",
            "seq": seq,
            "payload": {
                "msgId": msgId,
                "msgData": [
                    {
                        "msgHeader": {
                            "srcAddr": srcAddr,
                            "dstAddr": 1,
                            "frameId": frameId,  # 帧序号,每次发送递增
                            "optType": optType,
                            "optVer": 0,
                            "crcSum": generate_crcSum(data)
                        },
                        "time": get_utc_time(),
                        "data": base64_encode(data)
                    }
                ]
            }
        }
        return upMessage

    def resp_dnMessage(self, seq, msgId, status, srcAddr, dstAddr, frameId, optType, data):
        t = datetime.now().timestamp()
        utc_time = datetime.utcfromtimestamp(t).isoformat()  # UTC时间
        utc_time = utc_time.split(".")[0]
        crcSum = generate_crcSum(base64_decode_list(data))
        resp = {
            "resp": "DnMessage",
            "seq": seq,
            "status": status,
            "payload": {
                "msgId": msgId,
                "status": status,
                "msgData": [
                    {
                        "msgHeader": {
                            "srcAddr": srcAddr,
                            "dstAddr": dstAddr,
                            "frameId": frameId,
                            "optType": optType,
                            "optVer": 0,
                            "crcSum": crcSum
                        },
                        "time": utc_time,
                        "data": data
                    }
                ]
            }
        }
        return resp

    def update_firmware(self, seq, status):
        resp = {
            "resp": "UpdateFirmware",
            "seq": seq,
            "status": status,
            "payload": {
                "status": status
            }
        }
        return resp

ac_action.py
ac_action.py
import json

from datetime import datetime, timedelta
import requests
import pymysql
import base64
import hashlib

from ac_config import EneGatewayHost, sql_sentence, mysql_conf, BASIC_SALT


def get_mysql_conn(mysql_data):
    """获取mysql游标"""
    conn = pymysql.connect(
        host=mysql_data["host"],
        port=mysql_data["port"],
        user=mysql_data["user"],
        password=mysql_data["password"],
        db=mysql_data["database"],
        charset=mysql_data["charset"])
    cursor = conn.cursor()
    return cursor, conn


def get_pile_info(pile_sn):
    """获取桩的信息"""
    execute_sql = sql_sentence.replace("xxx", pile_sn)
    for mq in mysql_conf:
        cursor, conn = get_mysql_conn(mq)
        cursor.execute(execute_sql)
        res = cursor.fetchone()
        if res:
            return res
    else:
        return False


def calc_pile_Authorization(ctrlSn, pwd, mac):
    """生成鉴权码"""
    sha256 = hashlib.sha256()
    sha256.update(f"{ctrlSn}:{pwd}:{BASIC_SALT}:{mac.replace('-', ':').lower()}".encode('utf-8'))
    in_data = sha256.digest()
    res = 'Authorization:Basic ' + str(base64.b64encode((ctrlSn + ":" + base64.b64encode(in_data).decode("utf-8")).
                                                        encode('utf-8')).decode('utf-8'))
    return res


def get_connect_info(ene, pile_info):
    """
    获取连接信息,返回连接的url和header
    :param ene:         环境(china_dev,china_test,china_prod,England_test,USA_test,USA_prod,England_prod,Europe_prod)
    :param pile_info:   桩信息
    :return:
    """
    url = f"ws://{getattr(EneGatewayHost, ene)}/ws/websocket?sn={pile_info[0]}"
    ocpp_header = [pile_info[1], "Sec-WebSocket-Protocol:ocpp1.6", "Upgrade:websocket"]
    acmp_header = [pile_info[1], "Sec-WebSocket-Protocol:acmp1.0", "Upgrade:websocket"]
    return url, ocpp_header, acmp_header


def base64_encode(data):
    """base64对列表編碼"""
    if isinstance(data, list):
        bytes_data = bytes(data)  # 轉化成字節串
        base64_data = str(base64.b64encode(bytes_data), 'utf-8')
        return base64_data
    elif isinstance(data, str):
        base64_data = str(base64.b64encode(data.encode("utf-8")), "utf-8")
        return base64_data
    elif isinstance(data, dict):
        str_data = json.dumps(data)
        base64_data = str(base64.b64encode(str_data.encode('utf-8')), 'utf-8')
        return base64_data

    else:
        print("未知类型")


def base64_decode_list(data):
    """对经过base64编码的字节数组解密,返回列表"""
    li = list(base64.b64decode(data))
    # print("解密後:", n)
    return li


def base64_decode_json(data):
    """对经过base64编码的json数据密,返回字典"""
    data = json.loads(base64.b64decode(data))
    return data


def base64_decode_str(data):
    """对经过base64编码的数据解密,返回字符串"""
    data = str(base64.b64decode(data),'utf-8')
    return data


def get_utc_time():
    t = datetime.now().timestamp()
    utc_time = datetime.utcfromtimestamp(t).isoformat()  # UTC时间
    utc_time = utc_time.split(".")[0]
    return utc_time


def sixteen_to_ten(number):
    """16进制转10进制,入参需传字符串类型"""
    return int(number, 16)


def generate_time():
    """获取utc时间转成列表"""
    data_time = []  # 發生時間,第5到12位
    now_date = datetime.now()  # 獲取當前時間
    utc_time = now_date - timedelta(hours=8)  # 转成utc时间
    utc_time = str(utc_time)
    print(utc_time)
    data_time.append(int(utc_time[2:4]))
    data_time.append(int(utc_time[5:7]))
    data_time.append(int(utc_time[8:10]))
    data_time.append(int(utc_time[11:13]))
    data_time.append(int(utc_time[14:16]))
    data_time.append(int(utc_time[17:19]))
    data_time.append(int(utc_time[20:22]))
    data_time.append(0)
    return data_time


def generate_crcSum(data_list):
    crcSum = 0
    for i in data_list:
        crcSum += i
    return crcSum


def generate_businessType(data_list):
    data_list = base64_decode_list(data_list)
    businessType = data_list[0:2]
    return businessType


def event_data_encode(data):
    lis = [26, 0, 0, 0, 22, 11, 23, 7, 2, 17, 0, 0, 1, 0, 2, 117, 0]
    data = data
    data = base64_encode(data)
    data = base64_decode_list(data)
    data = lis + data
    data = base64_encode(data)
    print(data)


def event_data_decode(data):
    data = base64_decode_list(data)
    data_id = data[0:4]
    event_time = data[4:12]
    event_code = data[12:14]
    param_type = data[14:15]
    param_len = data[15:17]
    event_param = data[17:]
    event_param = base64_decode_str(base64_encode(event_param))
    print(event_param)


def download_firmware(url):
    url = url
    response = requests.get(url=url)
    with open('./test.aut', 'wb') as f:
        f.write(response.content)


def md5(data):
    m = hashlib.md5()
    m.update(data.encode(encoding='utf-8'))
    data_md5 = m.hexdigest()
    return data_md5


def get_token(ene, account, password):
    login_url = ene + "/api/saas-access-app/portal/login"
    data = {
        "account": account,
        "password": md5(password)
    }
    response = requests.post(url=login_url, json=data)
    token = response.json()['data']['token']
    return token


if __name__ == '__main__':
    le = base64_decode_str('eyJjb2RlIjogMTYzOTIsICJkYXRhT2JqIjogeyJQb3dlclN5c3RlbSI6ICIxIn19')
    print(le)

ac_common.py
ac_common.py
BASIC_SALT = ""
firmwareVersion = {
}  # 固件版本号:__UNI__OTA_ECC01/__UNI__OTA_ECP01


class EneGatewayHost:
    china_dev = ""  # 中国开发
    china_test = ""  # 中国测试
    china_prod = ""  # 中国生产
    England_test = ""  # 英国测试
    USA_test = ""  # 美西测试
    USA_prod = ""  # 美国生产
    England_prod = ""  # 英国生产
    Europe_prod = ""  # 欧洲生产
    Canada_prod = ""  # 加拿大生产
    ene_wb_dev_cn = ""  # 联调环境


class EneLoginHost:
    china_dev = ""  # 中国开发
    china_test = "n"  # 中国测试
    china_prod = ""  # 中国生产
    England_test = ""  # 英国测试
    USA_test = ""  # 美西测试
    USA_prod = ""  # 美国生产
    England_prod = ""  # 英国生产
    Europe_prod = ""  # 欧洲生产
    Canada_prod = ""  # 加拿大生产
    ene_wb_dev_cn = "n"  # 联调环境


class PlatformGatewayObj:
    china_test_platform = ""
    china_dev_platform = ""
    England_test_platform = ""


china_test_mysql = {
                    }  # 中国测试环境数据库配置
china_dev_mysql = {
                   }  # 中国开发环境数据库配置
England_test_mysql = {
                      }  # 英国测试环境数据库配置
mysql_conf = [china_test_mysql, china_dev_mysql, England_test_mysql]
ene_list = ['china_prod', 'china_test', 'china_dev', 'England_test', 'USA_test',
            'USA_prod', 'England_prod', 'Europe_prod', 'ene_wb_dev_cn']

sql_sentence = 'SELECT b.pile_sn AS sn,b.control_sn AS c_sn,a.ctrl_pwd AS pwd,a.mac AS mac,' \
               '(SELECT pin FROM pile_device WHERE sn = "xxx") AS pin FROM pile_device AS a INNER JOIN ' \
               'pile_device_relation AS b ON a.sn = b.control_sn HAVING b.pile_sn = "xxx";'


china_test_bill = {
                   }  # 中国测试环境数据库配置
china_dev_bill = {
                  }  # 中国开发环境数据库配置
England_test_bill = {
                     }  # 英国测试环境数据库配置
mysql_bill_conf = {
    "china_test": china_test_bill,
    "china_dev": china_dev_bill,
    "England_test": England_test_bill
}

user_info = {
    "china_test": {},
    "china_dev": {},
    "England_test": {},
}

ac_config.py
ac_config.py
import threading
import time

import websocket

from message import *


class AcPile(CcuMessage, AcuMessage, DtmMessage, ResponseMessage, ReportEvent, EcuMessage):

    def __init__(self, ene, sn, csn, password, mac, pin):
        self.sn, self.csn, self.password, self.mac, self.pin = sn, csn, password, mac, pin
        super().__init__()
        self.connect_flag = True  # 桩和服务器的连接状态,默认为连接状态的
        self.seq = 1  # seq为整数的字符串表示格式,递增,到最⼤值后归零,取值范围为uint64
        self.framId = 2  # 帧序号,每次发送递增
        self.powerSystem = 0  # 电源系统设置默认 为0 TN/TT供电系统,1为IT供电系
        self.gun1_status = 0
        self.gun2_status = 0
        self.resv_message = "接收消息"
        self.resp_message = "回复消息"
        self.au = calc_pile_Authorization(self.csn, self.password, self.mac)
        self.url, self.ocpp_header, self.acmp_header = get_connect_info(ene, (self.sn, self.au))
        self.acmp_ws = websocket.WebSocket()
        self.acmp_ws.connect(self.url, header=self.acmp_header)
        if self.sn[0] == 'D':
            threading.Thread(target=self.ac_dc_real_time_data).start()
            threading.Thread(target=self.ac_while_recv).start()
        else:
            threading.Thread(target=self.ac_real_time_data).start()
            threading.Thread(target=self.ac_while_recv).start()

    def ac_recv_msg(self):
        recv_message = self.acmp_ws.recv()
        print(f"{self.sn}:RECV:{recv_message}")
        return recv_message

    def ac_while_recv(self):
        """循环接受消息线程,一直收,死循环"""
        while 1:
            if self.connect_flag:  # 当连接状态为True的时候,可以一直收消息,进行后面的逻辑判断
                try:
                    recv_message = self.ac_recv_msg()  # 正常接收到的消息
                    if '"optType":1304' in recv_message:
                        self.resv_message = "接收平台消息:设置静音模式"
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        msgId = recv_data["payload"]["msgId"]
                        message = self.set_silent_mode(seq, 22, msgId)
                        # self.resp_message = message
                        self.ac_send_message(message)

                    elif '"optType":386' in recv_message:
                        recv_data = json.loads(recv_message)["payload"]["msgData"][0]["data"]
                        recv_data = base64_decode_json(recv_data)
                        if recv_data["code"] == 23:
                            self.resv_message = "接收平台消息:设置本地充电"
                            set_local_start = json.loads(recv_message)
                            seq = set_local_start["seq"]
                            msgId = set_local_start["payload"]["msgId"]
                            message = self.set_local_start(seq, 22, msgId)
                            self.ac_send_message(message)
                        elif recv_data["code"] == 25:
                            self.resv_message = "接收平台消息:查询本地充电"
                            sel_local_start = json.loads(recv_message)
                            seq = sel_local_start["seq"]
                            msgId = sel_local_start["payload"]["msgId"]
                            message = self.sel_local_start(seq, 22, msgId)
                            self.ac_send_message(message)
                        elif recv_data["code"] == 29:
                            set_time_zone = json.loads(recv_message)
                            seq = set_time_zone["seq"]
                            msgId = set_time_zone["payload"]["msgId"]
                            message = self.set_time_zone(seq, 22, msgId)
                            self.ac_send_message(message)

                        elif recv_data["code"] == 16389:
                            """设置供电系统,回复成功"""
                            print(recv_data, "收到设置供电系统消息")
                            set_power_system = json.loads(recv_message)
                            seq = set_power_system["seq"]
                            msgId = set_power_system["payload"]["msgId"]
                            message, statusType = self.set_power_system(seq, 22, msgId)
                            if statusType == "accept":
                                self.powerSystem = recv_data['dataObj']['PowerSystem']
                            self.ac_send_message(message)

                        elif recv_data["code"] == 16391:
                            """查询供电系统,回复1或0"""
                            query_power_system = json.loads(recv_message)
                            # # 电源系统设置默认 为0 TN/TT供电系统,1为IT供电系
                            if self.powerSystem == 0:
                                print(recv_data, f"收到查询供电系统消息,当前供电模式为:TN/TT")
                            else:
                                print(recv_data, f"收到查询供电系统消息,当前供电模式为:IT")
                            seq = query_power_system["seq"]
                            msgId = query_power_system["payload"]["msgId"]
                            message = self.query_power_system(seq, 22, msgId, str(self.powerSystem))
                            self.ac_send_message(message)
                        else:
                            pass
                    elif '"optType":369' in recv_message:
                        self.resv_message = "接收平台消息:设置日志级别"
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        msgId = recv_data["payload"]["msgId"]
                        srcAddr = recv_data["payload"]["msgData"][0]["msgHeader"]["dstAddr"]
                        message = self.set_log_level(seq, 32, msgId, srcAddr)
                        self.ac_send_message(message)

                    elif '"optType":372' in recv_message:
                        self.resv_message = "接收平台消息:查询日志级别"
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        msgId = recv_data["payload"]["msgId"]
                        srcAddr = recv_data["payload"]["msgData"][0]["msgHeader"]["dstAddr"]
                        message = self.sel_log_level(seq, 32, msgId, srcAddr)
                        self.ac_send_message(message)

                    elif '"optType":304' in recv_message:
                        self.resv_message = "接收平台消息:查询数据上报频率"
                        query_frequency_message = json.loads(recv_message)
                        data = query_frequency_message["payload"]["msgData"][0]["data"]
                        businessType = generate_businessType(data)
                        seq = query_frequency_message["seq"]
                        msgId = query_frequency_message["payload"]["msgId"]
                        # optType = query_frequency_message["payload"]["msgData"][0]["msgHeader"]["optType"] + 1
                        srcAddr = query_frequency_message["payload"]["msgData"][0]["msgHeader"]["dstAddr"]
                        message = self.sel_upload_frequency(seq, 33, msgId, srcAddr, businessType)
                        self.ac_send_message(message)

                    elif '"optType":306' in recv_message:
                        self.resv_message = "接收平台消息:设置数据上报频率"
                        set_frequency_message = json.loads(recv_message)
                        data = set_frequency_message["payload"]["msgData"][0]["data"]
                        businessType = generate_businessType(data)
                        seq = set_frequency_message["seq"]
                        msgId = set_frequency_message["payload"]["msgId"]
                        # optType = set_frequency_message["payload"]["msgData"][0]["msgHeader"]["optType"] + 1
                        srcAddr = set_frequency_message["payload"]["msgData"][0]["msgHeader"]["dstAddr"]
                        message = self.set_upload_frequency(seq, 33, msgId, srcAddr, businessType)
                        self.ac_send_message(message)

                    elif '"action":"UpdateFirmware"' in recv_message:
                        seq = json.loads(recv_message)["seq"]
                        status = "Accept"
                        message = self.update_firmware(seq, status)
                        self.ac_send_message(message)

                    elif '"optType":1570' in recv_message:
                        """禁用枪"""
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        data = recv_data["payload"]["msgData"][0]["data"]
                        gunNo = base64_decode_list(data)[0]
                        msgId = recv_data["payload"]["msgId"]
                        message = self.disable_pile(seq, 22, msgId, gunNo)
                        self.ac_send_message(message)
                        if gunNo == 1:
                            self.gun1_status = 7
                        if gunNo == 2:
                            self.gun2_status = 7
                        self.ac_send_message(self.report_charging_service_status(self.seq, 'UNAVAILABLE', gunNo))

                    elif '"optType":1572' in recv_message:
                        """解禁枪"""
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        data = recv_data["payload"]["msgData"][0]["data"]
                        gunNo = base64_decode_list(data)[0]
                        msgId = recv_data["payload"]["msgId"]
                        message = self.disable_pile(seq, 22, msgId, gunNo)
                        self.ac_send_message(message)
                        if gunNo == 1:
                            self.gun1_status = 0

                        if gunNo == 2:
                            self.gun2_status = 0
                        self.ac_send_message(self.report_charging_service_status(self.seq, 'AVAILABLE', gunNo))

                    else:
                        pass

                except Exception as e:
                    recv_message = ""  # 如果异常里的话定义recv_message为空,并且把self.connect_flag置为False
                    self.connect_flag = False
                    print(f"当前接收消息协程报错,报错信息为:{e}")
            elif not self.connect_flag:  # 如果连接状态为False,表示断连了,啥也不干,等待心跳线程重连,这里继续等待就行
                time.sleep(10)

    def ac_send_message(self, data):
        """发送消息"""
        message = json.dumps(data)
        self.acmp_ws.send(message)
        print(f"{self.sn}:SEND:{message}")
        self.seq += 1
        self.framId += 1

    def ac_send_pushrecord(self):
        rcType = int(input("请输入消息类型:"))
        if rcType == 3:
            # message = self.report_acu_event(self.seq)
            message = self.report_charging_service_status(self.seq)
            self.ac_send_message(message)
        elif rcType == 4:
            alert_code = input("请输入告警代码")
            message = self.report_alert(self.seq, alert_code)
            self.ac_send_message(message)

    def ac_send_acu1_realtata(self):
        message = self.acu_work_status(self.seq, 60, self.framId)
        self.ac_send_message(message)

    def ac_send_acu2_realtata(self):
        message = self.acu_work_status(self.seq, 61, self.framId)
        self.ac_send_message(message)

    def ac_send_ccu_realdata(self):
        self.ac_send_message(self.ccu_work_status(self.seq, 40, self.framId))
        self.ac_send_message(self.ccu_work_status(self.seq, 41, self.framId))
        self.ac_send_message(self.ccu_telemetry_data(self.seq, 40, self.framId))
        self.ac_send_message(self.ccu_telemetry_data(self.seq, 41, self.framId))
        self.ac_send_message(self.ccu_telesignalling_data(self.seq, 40, self.framId))
        self.ac_send_message(self.ccu_telesignalling_data(self.seq, 41, self.framId))
        self.ac_send_message(self.ccu_telemetry_charging_process(self.seq, 40, self.framId))
        self.ac_send_message(self.ccu_telemetry_charging_process(self.seq, 41, self.framId))

    def ac_send_ecu_realdata(self):
        self.ac_send_message(self.ecu_work_status(self.seq, self.framId))
        self.ac_send_message(self.ecu_telemetry_data(self.seq, self.framId))
        self.ac_send_message(self.ecu_telesignalling_data(self.seq, self.framId))
        self.ac_send_message(self.ecu_charging_data(self.seq, self.framId))

    def ac_send_dtm_realdata(self):
        self.ac_send_message(self.dtm_work_status(self.seq, self.framId))
        self.ac_send_message(self.pile_realtime_status(self.seq, self.framId, 1, self.gun1_status))
        self.ac_send_message(self.pile_realtime_status(self.seq, self.framId, 2, self.gun2_status))

    def ac_dc_real_time_data(self):
        """充电桩通过acmp需要发送的实时数据"""
        while 1:
            if self.connect_flag:  # 当连接状态为True的时候,可以一直发ccu状态数据
                try:
                    self.ac_send_dtm_realdata()
                    self.ac_send_ecu_realdata()
                    self.ac_send_ccu_realdata()
                    time.sleep(15)
                except Exception as e:  # 当发送消息失败的时候
                    print(f"ccu状态数据发送出错了,错误信息为:{e}")
                    self.connect_flag = False  # 发送消息发生错误了就把连接状态置为False
            elif not self.connect_flag:  # 连接状态为False了那么需要重新连接服务器
                try:
                    self.acmp_ws.connect(self.url, header=self.acmp_header)
                    self.connect_flag = True  # 如果重连成功了,那么连接状态置为True
                except Exception as e:
                    print(f"重连失败了,连接失败的信息为:{e}")
                    time.sleep(10)  # 10s后继续重连服务器

    def ac_real_time_data(self):
        """充电桩通过acmp需要发送的实时数据"""
        while 1:
            if self.connect_flag:  # 当连接状态为True的时候,可以一直发ccu状态数据
                try:
                    self.ac_send_acu1_realtata()
                    self.ac_send_acu2_realtata()
                    self.ac_send_message(self.dtm_work_status(self.seq, self.framId))
                    time.sleep(10)
                except Exception as e:  # 当发送消息失败的时候
                    print(f"acu状态数据发送出错了,错误信息为:{e}")
                    self.connect_flag = False  # 发送消息发生错误了就把连接状态置为False
            elif not self.connect_flag:  # 连接状态为False了那么需要重新连接服务器
                try:
                    self.acmp_ws.connect(self.url, header=self.acmp_header)
                    self.connect_flag = True  # 如果重连成功了,那么连接状态置为True
                except Exception as e:
                    print(f"重连失败了,连接失败的信息为:{e}")
                    time.sleep(10)  # 10s后继续重连服务器


if __name__ == '__main__':
    AcPile("china_test", "DE1240B1GN1C00029J")
    # SubscribePush("china_test", "DE1240B1GN1C00029J", "xiejieming@autel.com", "a12345678")

ac_pile.py
ac_pile.py
from tkinter import *
from MainPage import MainPage


class LoginPage:
    def __init__(self, master=None):
        self.root = master  # 定义内部变量root
        self.root.geometry('%dx%d' % (350, 200))  # 设置窗口大小
        self.ene = ["china_test", "china_dev", "china_prod", "England_test", "USA_test",
                    "USA_prod", "England_prod", "Europe_prod", "Canada_prod", "ene_wb_dev_cn",
                    "Osi_test"]
        self.Power_Phase = StringVar()
        self.pile_sn = StringVar()
        self.pile_csn = StringVar()
        self.ene_var = StringVar()
        self.createPage()

    def createPage(self):
        self.page = Frame(self.root)  # 创建Frame
        self.page.pack()
        Label(self.page).grid(row=0, stick=W)
        Label(self.page, text='桩   SN: ').grid(row=1, stick=W, pady=10)
        Entry(self.page, textvariable=self.pile_sn).grid(row=1, column=1, stick=E)
        # Label(self.page, text='桩CSN: ').grid(row=2, stick=W, pady=10)
        # Entry(self.page, textvariable=self.pile_csn).grid(row=2, column=1, stick=E)
        self.ene_var.set("选择连接环境")
        OptionMenu(self.page, self.ene_var, *self.ene).grid(row=2, stick=W, pady=10)
        self.Power_Phase.set("电源相数")
        OptionMenu(self.page, self.Power_Phase, "一相", "三相").grid(row=2, column=1, stick=E)
        Button(self.page, text='连接 服务器', bg='#7CCD7C', width=20, height=2, command=self.loginCheck).grid(row=3,
                                                                                                         columnspan=2,
                                                                                                         stick=E,
                                                                                                         pady=10)

    def loginCheck(self):
        pile_sn_list = self.pile_sn.get().split(";")
        pile_csn = self.pile_csn.get()
        connect_ene = self.ene_var.get()
        Power_Phase = self.Power_Phase.get()
        if connect_ene != "选择连接环" and Power_Phase != "电源相数":
            self.page.destroy()
            if Power_Phase == "三相":
                MainPage(pile_sn_list, pile_csn, connect_ene, 3, self.root)
            elif Power_Phase == "一相":
                MainPage(pile_sn_list, pile_csn, connect_ene, 1, self.root)
        else:
            pass
LoginPage.py
LoginPage.py
from oc_pile import OcPile
from ac_pile import AcPile

from oc_common import get_pile_info


class MainPile(AcPile, OcPile):
    def __init__(self, ene, pile_sn, pile_csn="", pile_flag=0):
        # pile_flag桩开始协议标志   0:开启OCPP+ACMP, 1:只开启OCPP,  2:只开始ACMP
        self.ene = ene
        self.sn = pile_sn
        if pile_csn == "":
            self.sn, self.csn, self.password, self.mac, self.pin = get_pile_info(self.sn)
            print(self.sn, self.csn, self.password, self.mac, self.pin)
        else:
            self.csn = pile_csn
        if pile_flag == 0:
            OcPile.__init__(self, self.ene, self.sn, self.csn, self.password, self.mac, self.pin)  # 调用 ocpp协议
            AcPile.__init__(self, self.ene, self.sn, self.csn, self.password, self.mac, self.pin)   # 调用 Acmp协议
        elif pile_flag == 1:
            OcPile.__init__(self, self.ene, self.sn, self.csn, self.password, self.mac, self.pin)  # 调用 ocpp协议
            print(self.ene, self.sn, self.csn, self.password, self.mac, self.pin)
        elif pile_flag == 2:
            AcPile.__init__(self, self.ene, self.sn, self.csn, self.password, self.mac, self.pin)  # 调用 Acmp协议


if __name__ == '__main__':
    import threading
    # pile = MainPile("china_test", "DE1240B1TEST00002J")
    # pile = MainPile("china_test", "AE0022A1GM8C35229E", pile_flag=1)
    # pile = MainPile("USA_test", "DG1480F1CN1C00018R")
    pile2 = MainPile("Canada_prod", "AL0007A1GN1C00063J", pile_flag=1)

main_pile.py
main_pile.py
import time
import os
from main_pile import MainPile
from tkinter import *
from threading import Thread

from PIL import Image, ImageTk

card_list = []


def reset_card_list():
    global card_list
    if not os.path.exists('Charging_card.txt'):
        card_list = []
    else:
        with open('Charging_card.txt', "r", encoding="utf-8") as f:
            cards = f.readlines()
            for card in cards:
                card_list.append(card.strip())


reset_card_list()


class MainPage:
    def __init__(self, pile_sn_list, pile_csn="", connect_ene="", Power_Phase="", master=""):
        self.root = master  # 定义内部变量root
        self.root.geometry('%dx%d' % (640, 320))  # 设置窗口大小
        self.pile_dic = {}
        self.pile_csn = pile_csn
        self.connect_ene = connect_ene
        self.Power_Phase = Power_Phase
        for pile_sn in pile_sn_list:
            pile = MainPile(self.connect_ene, pile_sn, self.pile_csn)
            pile.Power_Phase = self.Power_Phase  # 桩设置相位
            self.pile_dic[pile_sn] = pile
        # self.my_pile.Power_Phase = self.Power_Phase
        self.now_pile = [pile for pile in self.pile_dic.values()][0]
        self.root.title(f"{self.connect_ene}:充电桩SN:{self.now_pile.sn}:{self.Power_Phase}相")
        self.root.geometry('640x320')
        self.var = StringVar()
        self.var.set('枪状态切换')
        self.dstr = StringVar()  # 第一行的全部信息
        self.dian_liu = StringVar()  # 桩的电流
        self.dian_ya = StringVar()  # 桩的电压
        self.dian_liu.set(self.now_pile.change_electric)
        self.dian_ya.set(self.now_pile.change_voltage)
        self.Fault_message = StringVar()  # 故障通知
        self.Fault_message.set("故障 类型")
        self.Tampering_Notification = StringVar()  # 防篡改通知消息
        self.Tampering_Notification.set('篡改 通知')
        self.soc_value = StringVar()  # soc值
        self.soc_rate = StringVar()  # soc增长速度
        self.card_value = StringVar()  # 刷卡启动的卡号
        self.choosePile = StringVar()  # 选择充电桩
        self.choosePile.set("ChoosePile")
        self.deletePile = StringVar()  # 关闭启动得充电桩
        self.deletePile.set("DeletePile")
        self.createPile = StringVar()  # 新增充电桩
        self.alivePiles = StringVar()  # 当前存活的桩的数量
        self.createPage()

    def __del__(self):
        print("执行析构函数")

    def enter_card(self, card):
        global card_list
        if not os.path.exists('Charging_card.txt'):
            f = open('./Charging_card.txt', 'w', encoding="utf-8")
            f.close()
        with open('Charging_card.txt', "a", encoding="utf-8") as f1:
            f1.write(card + '\n')
            card_list.append(card)

    def calc(self):
        """充电时不让切换状态"""
        state = self.var.get()
        pile_dian_liu = self.dian_liu.get()
        pile_dian_ya = self.dian_ya.get()
        if self.now_pile.change_flag:
            self.now_pile.change_electric = pile_dian_liu
            self.now_pile.change_voltage = pile_dian_ya
            self.now_pile.change_energy = int(int(pile_dian_liu) * int(pile_dian_ya)) * self.Power_Phase
        else:
            self.now_pile.change_electric = pile_dian_liu
            self.now_pile.change_voltage = pile_dian_ya
            self.now_pile.change_energy = int(int(pile_dian_liu) * int(pile_dian_ya)) * self.Power_Phase
            if state == "枪状态切换":
                pass
            else:
                self.now_pile.oc_change_pile_state(state)

    def Card_start_Charging(self):
        card_id = self.card_value.get()
        if card_id:
            if self.Card_button['text'] == "刷卡 启动充电":
                self.Card_button['text'] = '刷卡 停止充电'
                print(f"刷{card_id}启动充电")
                self.now_pile.oc_send_message("POS_RULE", 2, pileSN=self.now_pile.sn)  # 发送第一条消息启动充电的消息
                time.sleep(0.1)
                self.now_pile.oc_send_message("card_Authorize", 2, card_id=card_id)  # 刷卡启动充电
                self.now_pile.card_Charging_flag = True  # 刷卡启动的标识置为True
                Thread(target=self.now_pile.oc_card_Charging, args=(card_id,)).start()
            else:
                self.Card_button['text'] = '刷卡 启动充电'
                print(f"刷{card_id}卡停止充电")
                self.now_pile.card_Charging_flag = False

    def Pause_charging(self):
        if self.Pause_button['text'] == '暂停 充电':
            self.Pause_button['text'] = '启动 充电'
            self.now_pile.Pause_charging_flag = True  # 暂停充电的标志为True
            self.now_pile.oc_send_message("SuspendedEV", self.now_pile.requests_type2, pile_connectorId=1)  # 发送车暂停状态
            print('暂停 充电')
        else:
            self.Pause_button['text'] = '暂停 充电'
            self.now_pile.Pause_charging_flag = False  # 暂停充电的标志为False
            print('启动 充电')

    def Send_Fault(self):
        """故障上报处理"""
        print(self.Fault_message.get())

    def Tamper_Notification(self):
        if self.Tampering_Notification.get() == "SECURITY_EVENT":
            self.now_pile.oc_send_message("SECURITY_EVENT", 2, seq=101, eventType=1, securityHappen=1)
            print("吊毛,你的桩被篡改了")

    def preview(self):
        global tk_image
        image = Image.open(f"./PilePicDir/{self.now_pile.sn}.png")
        tk_image = ImageTk.PhotoImage(image)
        top = Toplevel()
        top.title(f'{self.now_pile.sn}的二维码')
        top.geometry('+830+400')
        Label(top, image=tk_image).pack()

    def alive_pile(self):
        in_str = f"当前存活的充电桩:{len(self.pile_dic)}"
        self.alivePiles.set(in_str)
        self.root.after(1000, self.alive_pile)

    def pile_info(self):
        # 获取当前时间
        start_time = int(time.time() - self.now_pile.start_time)
        min = 60
        hour = 60 * 60
        day = 60 * 60 * 24
        DAY = start_time // day
        HOUR = (start_time - (DAY)) // hour
        MINUT = (start_time - (DAY + (HOUR * hour))) // min
        SECONDS = start_time - (DAY + (HOUR * hour) + (MINUT * min))
        str_time = f'UPTIME:{HOUR:0>2}:{MINUT:0>2}:{SECONDS:0>2}'

        # 获取枪状态
        if self.now_pile.change_flag is True:
            str_CurrentState = f'CurrentState:Charging!!!'
        elif self.now_pile.pile_state == "Preparing":
            str_CurrentState = f'CurrentState:Preparing,请扫码充电'
        else:
            str_CurrentState = f'CurrentState:{self.now_pile.pile_state}!!!'

        # 获取电流和电压
        str_electric_voltage = f'CUR:{self.now_pile.change_electric}A/Volta:{self.now_pile.change_voltage}V'
        in_str = f"{str_time}    {str_electric_voltage}    {str_CurrentState}"
        self.dstr.set(in_str)
        self.root.after(1000, self.pile_info)

    def enter_soc_data(self):
        soc_value = self.soc_value.get()
        soc_rate = self.soc_rate.get()
        self.now_pile.soc_value = round(float(soc_value), 1)  # 浮点数
        self.now_pile.soc_rate = round(float(soc_rate), 1)  # # 浮点数
        print(f"当前车的soc值为:{self.now_pile.soc_value}/{self.now_pile.soc_rate}")

    def choose_pile_callback(self, *args):
        self.now_pile = self.pile_dic[self.choosePile.get()]
        self.root.title(f"{self.connect_ene}:充电桩SN:{self.now_pile.sn}:{self.Power_Phase}相")
        print(f"Choose Success, NowPile :{self.now_pile.sn}")

    def delete_pile_callback(self):
        delete_pile_sn = self.deletePile.get()
        if delete_pile_sn in [pile for pile in self.pile_dic.keys()] and len(
                [pile for pile in self.pile_dic.keys()]) >= 2:
            delete_pile = self.pile_dic[delete_pile_sn]  # 要被删除的桩
            if self.now_pile.change_flag is not True:   # 如果充电桩没有处于充电才能删除
                delete_pile.pileRunFlag = False  # 修改桩的运行状态为False,那么他的运行状态就凉了
                del delete_pile  # 删除实例对象
                del self.pile_dic[delete_pile_sn]  # 删除运行的桩列表
                delattr(self, "choosePileOpt")  # 删除老的
                self.choosePileOpt = OptionMenu(self.root, self.choosePile,
                                                *[pile for pile in self.pile_dic.keys()]).grid(row=2, column=3, sticky="w")
                # self.choosePile.trace("w", self.choose_pile_callback)
                delattr(self, "deletePileOpt")  # 删除老的
                self.deletePileOpt = OptionMenu(self.root, self.deletePile,
                                                *[pile for pile in self.pile_dic.keys()]).grid(row=3, column=1, sticky="w")
                print(f"桩:{delete_pile_sn} 删除成功")
            else:
                print("傻逼,充电中别删")

    def create_pile_callback(self):
        """创建一个桩"""
        create_pile_sn = self.createPile.get()
        if create_pile_sn not in [pile for pile in self.pile_dic.keys()]:
            pile = MainPile(self.connect_ene, create_pile_sn)
            pile.Power_Phase = self.Power_Phase  # 桩设置相位
            self.pile_dic[create_pile_sn] = pile
            delattr(self, "choosePileOpt")  # 删除老的
            self.choosePileOpt = OptionMenu(self.root, self.choosePile,
                                            *[pile for pile in self.pile_dic.keys()]).grid(row=2, column=3, sticky="w")
            # self.choosePile.trace("w", self.choose_pile_callback)

            delattr(self, "deletePileOpt")  # 删除老的
            self.deletePileOpt = OptionMenu(self.root, self.deletePile,
                                            *[pile for pile in self.pile_dic.keys()]).grid(row=3, column=1, sticky="w")
            print("新增充电桩成功!!!!!!!")
        else:
            print("傻逼,别重复添加,已经有了")

    def createPage(self):
        self.page = Frame(self.root)
        Label(self.root, textvariable=self.dstr, fg='orange', font=("微软雅黑", 11)).grid(row=1, column=1, columnspan=4,
                                                                                      sticky="w",
                                                                                      padx=2,
                                                                                      pady=1)
        Label(self.root, textvariable=self.alivePiles, fg='red', font=("微软雅黑", 11)).grid(row=2, column=1, columnspan=2,
                                                                                         sticky="w",
                                                                                         padx=2,
                                                                                         pady=1)

        self.choosePileOpt = OptionMenu(self.root, self.choosePile,
                                        *[pile for pile in self.pile_dic.keys()]).grid(row=2, column=3, sticky="w")
        self.choosePile.trace("w", self.choose_pile_callback)
        Label(self.root, height=1, font=("微软雅黑", 12), fg='blue', text="切换 充电桩").grid(row=2, column=4, sticky="w")

        # 开启充电桩+删除充电桩逻辑
        self.deletePileOpt = OptionMenu(self.root, self.deletePile,
                                        *[pile for pile in self.pile_dic.keys()]).grid(row=3, column=1, sticky="w")
        self.deletePile_button = Button(self.root, text="删除 充电桩", bg='#7CCD7C', width=15, height=1,
                                        command=self.delete_pile_callback)
        self.deletePile_button.grid(row=3, column=2, sticky="w")

        Entry(self.root, textvariable=self.createPile).grid(row=3, column=3, sticky="w")
        self.createPile_button = Button(self.root, text="新增 充电桩", bg='#7CCD7C', width=15, height=1,
                                        command=self.create_pile_callback)
        self.createPile_button.grid(row=3, column=4, sticky="w")

        OptionMenu(self.root, self.Fault_message, "故障1", "......").grid(row=4, column=1, sticky="w")
        Button(self.root, text="发送 故障通知", bg='#7CCD7C', width=15, height=1,
               command=self.Send_Fault).grid(row=4, column=2, sticky="w")

        self.Pause_button = Button(self.root, text="暂停 充电", bg='#7CCD7C', width=15, height=1,
                                   command=self.Pause_charging)
        self.Pause_button.grid(row=4, column=4, sticky="w")

        Label(self.root, height=1, font=("微软雅黑", 12), fg='red', text="录入SOC:Value/Rate").grid(row=5, column=1,
                                                                                              sticky="w")
        Entry(self.root, textvariable=self.soc_value).grid(row=5, column=2, sticky="w")
        Entry(self.root, textvariable=self.soc_rate).grid(row=5, column=3, sticky="w")
        self.soc_button = Button(self.root, text="录入SOC", bg='#7CCD7C', width=15, height=1,
                                 command=self.enter_soc_data)
        self.soc_button.grid(row=5, column=4, sticky="w")

        Entry(self.root, textvariable=self.card_value).grid(row=6, column=1, sticky="w")  # 刷卡启动,充电卡号码
        self.Card_button = Button(self.root, text="刷卡 启动充电", bg='#7CCD7C', width=15, height=1,
                                  command=self.Card_start_Charging)
        self.Card_button.grid(row=6, column=2, sticky="w")

        OptionMenu(self.root, self.Tampering_Notification, "SECURITY_EVENT").grid(row=6, column=3, sticky="w")
        Button(self.root, text="发送 篡改通知", bg='#7CCD7C', width=15, height=1,
               command=self.Tamper_Notification).grid(row=6, column=4, sticky="w")

        Label(self.root, height=1, font=("微软雅黑", 12), fg='blue', text="电    流:").grid(row=7, column=1, sticky="w")
        Entry(self.root, textvariable=self.dian_liu).grid(row=7, column=2, sticky="w")
        Label(self.root, height=1, font=("微软雅黑", 12), fg='blue', text="电    压:").grid(row=7, column=3, sticky="w")
        Entry(self.root, textvariable=self.dian_ya).grid(row=7, column=4, sticky="w")

        oMenu = OptionMenu(self.root, self.var,
                           "Heartbeat",
                           "Available",
                           "Preparing",
                           "Faulted",
                           "Reserved",
                           "SuspendedEVSE",
                           "SuspendedEV",
                           "Finishing",
                           "Unavailable",
                           "Charging")
        oMenu.config(width=20, height=2)
        oMenu.grid(row=8, column=1, columnspan=2, padx=2, pady=1)
        Button(self.root, text="桩二维码", width=30, height=2, command=self.preview).grid(row=8, column=3, columnspan=2,
                                                                                      sticky=SE,
                                                                                      padx=2,
                                                                                      pady=1)

        Button(self.root, text="确     定", bg='#7CCD7C', width=40, height=2, command=self.calc).grid(row=9, column=2,
                                                                                                    columnspan=3,
                                                                                                    sticky="w", padx=2,
                                                                                                    pady=1)
        self.pile_info()
        self.alive_pile()

MainPage.py
MainPage.py
import sqlite3

import random
import string
import json

from ac_action import AcmpMessage
from ac_common import *


class CcuMessage(AcmpMessage):

    def ccu_work_status(self, seq, srcAddr, frameId):
        """8.5.1.1,CCU_主动上报工作状态数据,业务类型为0x0400"""
        work_status_list = [0, 1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150]
        work_status = random.choice(work_status_list)
        # pile_status = [0]
        # status = [0]
        # reserved = [0]
        memory_usage = random.randint(0, 100)
        maximum_task_stack_usage = random.randint(0, 100)
        maximum_task_stack = [84, 101, 115, 116, 84, 101, 115, 116, 84, 101, 115, 116, 84, 101, 115, 116, ]
        data = [work_status, 0, 0, 0, memory_usage, maximum_task_stack_usage] + maximum_task_stack
        optType = 1024
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage

    def ccu_telemetry_data(self, seq, srcAddr, frameId):
        """CCU_主动上报遥测数据,业务类型为0x0401"""
        data = [189, 4, 232, 3, 0, 0, 1, 0, 103, 9, 0, 0, 0, 0, 50, 0, 78, 50, 249, 255, 0, 0, 0, 0, 20, 5, 0, 0, 200,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        optType = 1025
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage

    def ccu_telemetry_charging_process(self, seq, srcAddr, frameId):
        """CCU_主动上报遥测(充电过程)数据,业务类型为0x0402"""
        data = [251, 255, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        optType = 1026
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage

    def ccu_telesignalling_data(self, seq, srcAddr, frameId):
        """CCU_主动上报遥信数据,业务类型为0x0403"""
        data = [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        optType = 1027
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage


class EcuMessage(AcmpMessage):
    def ecu_work_status(self, seq, frameId):
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 96, 96, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        message = self.action_upMessage(seq, msgId, 30, frameId, 1280, data)
        return message

    def ecu_telemetry_data(self, seq, frameId):
        """ECU_主动上报遥测数据,业务类型为0x0501"""
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [0, 0, 26, 0, 28, 0, 27, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        message = self.action_upMessage(seq, msgId, 30, frameId, 1281, data)
        return message

    def ecu_telesignalling_data(self, seq, frameId):
        """ECU_主动上报遥信数据,业务类型为0x0502"""
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        message = self.action_upMessage(seq, msgId, 30, frameId, 1282, data)
        return message

    def ecu_charging_data(self, seq, frameId):
        """ECU_主动上报充电模块数据,业务类型0x0503"""
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [1, 0, 0, 16, 40, 108, 40, 40, 40, 149, 156, 6, 255, 50, 49, 48, 50, 51, 49, 50, 86, 71, 81, 75, 88, 0,
                1, 9, 1, 9, 1, 1, 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, 139, 19, 133, 0, 100, 0, 239, 15, 217, 15, 3, 16,
                0, 0, 31, 0, 27, 0, 0, 0, 0, 0, 0, 64, 0, 0, 2, 0, 0, 132, 0, 0, 0, 0, 0, 0, 0]
        message = self.action_upMessage(seq, msgId, 30, frameId, 1283, data)
        return message


class DtmMessage(AcmpMessage):

    def dtm_work_status(self, seq, frameId):
        """8.7.1.1 DTM_工作状态上报数据,业务类型为0x0600"""
        data = [0, 0, 0, 0, 0, 0, 0, 2, 28, 0, 0, 0, 4, 0, 0, 0, 5]
        optType = 1536
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        message = self.action_upMessage(seq, msgId, 10, frameId, optType, data)
        return message

    def pile_realtime_status(self, seq, frameId, gunNo, status):
        """8.7.1.2  DTM_充电接口相关实时数据上报,业务类型为0x0601"""
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [gunNo, status, 0, 0, 48, 1, 36, 0, 0, 0, 3, 0, 0, 2, 0, 0, 0, 0, 0, 1]
        message = self.action_upMessage(seq, msgId, 10, frameId, 1537, data)
        return message

    def upgrade_firmware_status(self, seq, frameId, upgrade_status):
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        srcAddr = 10
        optType = 1584
        data = [upgrade_status, 10]  # 第二位总体进度
        message = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return message


class AcuMessage(AcmpMessage):

    def acu_work_status(self, seq, srcAddr, frameId):
        """8.9.1.1  ACU_主动上报工作状态数据,业务类型为0x0700"""
        work_status_list = [0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
        work_status = random.choice(work_status_list)  # 工作状态
        pile_status = 0  # 充电桩状态 00H:车辆未连接   01H:车辆已连接,02H:参数握手阶段,03H:绝缘监测阶段,04H:参数辨识阶段,05H:参数配置阶段,06H:预充电阶段,
        # 07H:正式充电阶段,08H:充电暂停,09H:充电停止中 ,0AH:充电停止完成,0BH:充电完成
        vehicle_connection_status = 1  # 车辆连接状态,0:已连接  1:未连接
        cp_status = 0  # CP状态,0x00:CP异常,0x01H:12V,0x02H:9V <br /> 0x03H:6V,0x04H:接地
        pp_status = 0  # PP状态,0x00H:断开,0x01H:13A,0x02H:20A,0x03H:32A,0x04H:63A,0x05H:异常
        electronic_lock_condition = 0  # 电子锁状态,00H:解锁状态,01H:上锁状态,02H:异常状态
        contactor_status = 0  # 接触器状态,00H:断开,01H:闭合
        power_off_condition = 0  # 掉电状态,00H:掉电状态,01H:上电状态
        memory_usage = random.randint(0, 100)  # 系统动态内存使用百分比
        maximum_task_stack_usage = random.randint(0, 100)  # 任务堆栈最大使用百分比
        maximum_task_stack = [84, 101, 115, 116, 84, 101, 115, 116, 84, 101, 115, 116, 84, 101, 115,
                              116, ]  # 任务名字,ASCII,16
        data = [work_status, pile_status, vehicle_connection_status, cp_status, pp_status, electronic_lock_condition,
                contactor_status, power_off_condition, memory_usage, maximum_task_stack_usage] + maximum_task_stack
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        optType = 1792
        utc_time = get_utc_time()
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage

    def acu_telemetry_data(self, seq, srcAddr, frameId):
        pass

    def acu_signaling_data(self):
        pass


class ResponseMessage(AcmpMessage):
    def __init__(self):
        self.conn = sqlite3.connect('test.db')
        self.cursor = self.conn.cursor()

    def sel_local_start(self, seq, frameId, msgId):
        """查询本地充电应答"""
        status = "Accept"
        srcAddr = "10"
        dstAddr = 1
        optType = 387
        GetLocalStartReply = 2
        data = f'{{"code":26,"dataObj":{{"GetLocalStartReply":{GetLocalStartReply}}},"statusType":"accept"}}'
        # 0云端允许,桩允许、1云端允许,桩禁止、2云端禁止,桩禁止
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_local_start(self, seq, frameId, msgId):
        """设置本地充电应答"""
        status = "Accept"
        srcAddr = "10"
        dstAddr = 1
        optType = 387
        data = self.cursor.execute(("SELECT value FROM message_config where name = '设置本地充电应答';"))
        data = data.fetchone()
        data = data[0]
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def sel_upload_frequency(self, seq, frameId, msgId, srcAddr, businessType):
        """查询数据上传频率应答"""
        status = "Accept"
        srcAddr = srcAddr
        dstAddr = 1
        optType = 305
        data = [0]
        data = data + businessType + [6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_upload_frequency(self, seq, frameId, msgId, srcAddr, businessType):
        """设置数据上传频率应答"""
        status = "Accept"
        srcAddr = srcAddr
        dstAddr = 1
        optType = 307
        data = [0]
        data = data + businessType + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def sel_log_level(self, seq, frameId, msgId, srcAddr):
        """查询日志上传等级应答"""
        status = "Accept"
        srcAddr = srcAddr
        dstAddr = 1
        optType = 373
        data = [4]  # 1-fault 2-warning 3-info 4-debug
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_log_level(self, seq, frameId, msgId, srcAddr):
        status = "Accept"
        """设置日志上传等级应答"""
        srcAddr = srcAddr
        dstAddr = 1
        optType = 370
        data = [0]  # 0:表示成功;  1:表示失败
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_silent_mode(self, seq, frameId, msgId):
        """设置静音模式"""
        status = "Accept"
        srcAddr = 30
        dstAddr = 1
        optType = 1305
        data = [0]  # 0表示成功;  其它值表示失败原因
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def disable_pile(self, seq, frameId, msgId, gunNo):
        """禁用充电枪"""
        status = "Accept"
        srcAddr = 10
        dstAddr = 1
        optType = 1571
        data = [gunNo, 0, 48, 85, 58, 124, 0, 0, 0, 32]  # 0表示成功;  其它值表示失败原因
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_time_zone(self, seq, frameId, msgId):
        """设置时区"""
        status = "Accept"
        srcAddr = "10"
        dstAddr = 1
        optType = 387
        data = '{"code":30,"statusType":"accept"}'
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_power_system(self, seq, frameId, msgId):
        """设置供电系统"""
        status = "Accept"
        srcAddr = 60
        dstAddr = 1
        optType = 387
        data = '{"code":16390,"statusType":"accept","dataObj":""}'
        # data = '{"code":16390,"statusType":"reject","dataObj":""}'
        statusType = json.loads(data)['statusType']
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message, statusType

    def query_power_system(self, seq, frameId, msgId, type):
        """查询供电系统,type:1表示IT系统, 0表示TN/TT系统"""
        status = "Accept"
        srcAddr = 60
        dstAddr = 1
        optType = 387
        data = {"code": 16392, "dataObj": {"PowerSystem": type}}
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message


class ReportEvent(AcmpMessage):

    def report_alert(self, seq, code):
        """输入4位告警代码生成告警消息并用base64加密,并通过告警代码匹配模块"""
        unitId = 10
        if code[0] == '0':
            unitId = 10  # DTM
        elif code[0] == '1':
            unitId = 20  # APK
        elif code[0] == '2':
            unitId = 40  # CCU
        elif code[0] == '3':
            unitId = 30  # ECU
        elif code[0] == '4':
            unitId = 60  # ACU
        else:
            print("未知模块")
        number = [3, 0, 0, 0]  # 記錄序號,第1到4位
        alert_level = [0]  # 故障級別,第13位
        alert_code = []  # 故障代碼,第14到15位
        alert_code.append(sixteen_to_ten(code[2:4]))
        alert_code.append(sixteen_to_ten(code[0:2]))
        data_type = [255]  # 發生或恢復,255是發生告警,0表示告警恢復,第16位
        data_len = [0, 0]  # 参数长度,第17到18位
        initial_data = number + generate_time() + alert_level + alert_code + data_type + data_len
        base64_data = base64_encode(initial_data)
        rcType = 4
        message = self.action_pushrecord(rcType, seq, unitId, base64_data)
        return message

    def report_charging_service_status(self, seq, status, gunNo):
        rcType = 3
        # 0-AVAILABLE,1-PREPARING,2-CHARGING,3-SUSPEND_EV,4-SUSPEND_EVSE,5-FINISHING,6-RESERVED,7-UNAVAILABLE,8-FAULTED
        params = f'{{"connectorId":{gunNo},"connectorState":"{status}","prevStateType":0,"subState":1,"runStatus":3,"cpStatus":3,"pileState":0}}'
        event_id = [1, 12, 2, 1]
        report_time = generate_time()
        event_code = [1, 0]
        param_type = [2]
        params_list = base64_decode_list(base64_encode(params))
        params_list_len = [len(params), 0]
        data = base64_encode(event_id + report_time + event_code + param_type + params_list_len + params_list)
        message = self.action_pushrecord(rcType, seq, 10, data)
        print(message)
        return message

    def report_ccu_event(self, seq, param, unitId):
        rcType = 3
        params = f'rebootType={param}'
        event_id = [1, 12, 2, 1]
        report_time = generate_time()
        event_code = [1, 32]
        param_type = [1]
        params_list = base64_decode_list(base64_encode(params))
        params_list_len = [len(params), 0]
        data = base64_encode(event_id + report_time + event_code + param_type + params_list_len + params_list)
        message = self.action_pushrecord(rcType, seq, unitId, data)
        return message

    def report_acu_event(self, seq, param, unitId):
        rcType = 3
        params = f'rebootType={param}'
        event_id = [2, 1, 2, 1]
        report_time = generate_time()
        event_code = [1, 64]
        param_type = [1]
        params_list = base64_decode_list(base64_encode(params))
        params_list_len = [len(params), 0]
        data = base64_encode(event_id + report_time + event_code + param_type + params_list_len + params_list)
        message = self.action_pushrecord(rcType, seq, unitId, data)
        return message

    def report_ecu_event(self, seq, param):
        rcType = 3
        params = f'rebootType={param}'
        event_id = [2, 1, 2, 1]
        report_time = generate_time()
        event_code = [1, 48]
        param_type = [1]
        params_list = base64_decode_list(base64_encode(params))
        params_list_len = [len(params), 0]
        data = base64_encode(event_id + report_time + event_code + param_type + params_list_len + params_list)
        message = self.action_pushrecord(rcType, seq, 30, data)
        return message

    def report_order(self, gunNo, ):
        data = {
            "connectorId": gunNo,
            "LocalId": "DE1240B1GN1C00038J:1:639b7928:0",
            "version": 2,
            "RemoteId": "",
            "startIdTag": "POS3334353637011006",
            "stopIdTag": "POS3334353637011006",
            "startMode": 7,
            "chargeMode": 0,
            "chargeParam": 0,
            "chargeTimeSec": 1,
            "stopTime": 0,
            "meterStart": 2362400,
            "meterEnd": 2362400,
            "energy": 0,
            "stopReason": 1034,
            "accountFlag": 0,
            "svcEndFlag": 1,
            "startTranFlag": 0,
            "stopTranFlag": 0,
            "svcStartTime": "2022-12-16T03:44:40",
            "svcEndTime": "2022-12-16T03:44:41",
            "billing": {
                "startSoc": 58,
                "endSoc": 58,
                "startBalance": 10000,
                "endBalance": 10000,
                "carVin": "",
                "billTemplate": "AutelLocalTemplate",
                "totalMoney": 0,
                "svcMoney": 0,
                "chargeMoney": 0,
                "sectNum": 1,
                "sect": [{
                    "sectIndex": 0,
                    "sectAttribute": 127,
                    "sectEnergy": 0,
                    "svcMoneyUnit": 0,
                    "chargeMoneyUnit": 20000,
                    "sectStartTime": "03:44:40",
                    "sectStopTime": "03:44:41"
                }]
            }
        }


if __name__ == '__main__':
    print(CcuMessage().ccu_work_status(3, 3, 13))

    # a = ResponseMessage().set_local_start(32, 32, 3)
    # print(a)

message.py
message.py
import os
import logging
from datetime import datetime, timedelta
import random

import qrcode
import pymysql
import base64
import hashlib
import requests

from oc_conf import Message
from oc_conf import EneGatewayHost, sql_sentence, mysql_conf, BASIC_SALT, mysql_bill_conf, PlatformGatewayObj, user_info


def getUTC0():
    """返回:2022-07-06T00:36:39.000Z"""
    from datetime import datetime, timedelta
    now_time = datetime.now()
    utc_time = now_time - timedelta(hours=8)  # UTC只是比北京时间提前了8个小时
    utc_time = utc_time.strftime("%Y-%m-%dT%H:%M:%S" + ".000Z")
    return utc_time


def get_utc0_time():
    """获取UTC0时区时间,返回:20220706003708"""
    utc_time = datetime.now() - timedelta(hours=8)
    utc_time = utc_time.strftime("%Y%m%d%H%M%S")
    return utc_time


def get_mysql_conn(mysql_data):
    """获取mysql游标"""
    conn = pymysql.connect(
        host=mysql_data["host"],
        port=mysql_data["port"],
        user=mysql_data["user"],
        password=mysql_data["password"],
        db=mysql_data["database"],
        charset=mysql_data["charset"])
    cursor = conn.cursor()
    return cursor, conn


def get_pile_info(pile_sn):
    """获取桩的信息"""
    execute_sql = sql_sentence.replace("xxx", pile_sn)
    # print(execute_sql)
    for mq in mysql_conf:
        cursor, conn = get_mysql_conn(mq)
        cursor.execute(execute_sql)
        res = cursor.fetchone()
        if res:
            # print(res, 1111111)
            return res
    else:
        return False


def md5_encryption(in_data):
    md5 = hashlib.md5()
    md5.update(in_data.encode("utf8"))
    return md5.hexdigest()


def clear_abnormal_order(pile_sn, in_ene):
    print("clear abnormal order, please wait......")
    try:
        order_cursor, order_conn = get_mysql_conn(mysql_bill_conf[in_ene])
        sql_1 = f'SELECT order_seq FROM tb_bill WHERE evse_sn LIKE "{pile_sn}%" AND order_status NOT IN (2, 99);'
        sql_2 = f'SELECT order_seq FROM tb_bill WHERE evse_sn LIKE "{pile_sn}%" ' \
                f'AND pay_status NOT IN (3, 4) and order_status NOT IN (2, 99);'
        order_cursor.execute(sql_1)
        order_number_list1 = list(order_cursor.fetchall())
        if order_number_list1:
            for order in order_number_list1:
                updateBillStatusManual(in_ene, order[0])
        order_cursor.execute(sql_2)
        order_number_list2 = list(order_cursor.fetchall())
        if order_number_list2:
            for order in order_number_list2:
                updateBillStatusManual(in_ene, order[0])
        order_cursor.close()
        order_conn.close()
    except Exception as e:
        print(f"傻逼,只能清除测试环境啊:{e}")


def get_token(in_ene):
    """
    登录获取各环境的token,默认中国测试环境
    :param in_ene:        xx环境的platform(china_test, china_prod, England_test等)
    :return:
    """
    login_host = getattr(PlatformGatewayObj, in_ene + "_platform") + "/api/saas-access-app/portal/login"
    payload = {
        "account": user_info[in_ene]["username"],
        "password": md5_encryption(user_info[in_ene]["password"]),
        "isEncryption": 2,
        "loginFrom": "WEB"
    }
    res = requests.post(url=login_host, json=payload)
    return res.json()['data']['token']


def updateBillStatusManual(in_ene, order_seq):
    url = f"http://{getattr(EneGatewayHost, in_ene)}/api/pile-bill-app/ocpp/updateBillStatusManual"
    payload = {
        "busId": str(order_seq),
        "status": 99
    }
    headers = {
        "Authorization": get_token(f"{in_ene}")
    }
    reps = requests.put(url, json=payload, headers=headers)
    return reps.json()


def calc_pile_Authorization(ctrlSn, pwd, mac):
    sha256 = hashlib.sha256()
    sha256.update(f"{ctrlSn}:{pwd}:{BASIC_SALT}:{mac.replace('-', ':').lower()}".encode('utf-8'))
    in_data = sha256.digest()
    res = 'Authorization:Basic ' + str(base64.b64encode((ctrlSn + ":" + base64.b64encode(in_data).decode("utf-8")).
                                                        encode('utf-8')).decode('utf-8'))
    return res


def get_connect_info(ene, pile_info):
    """
    获取连接信息,返回连接的url和header
    :param ene:         环境(china_dev,china_test,china_prod,England_test,USA_test,USA_prod,England_prod,Europe_prod)
    :param pile_info:   桩信息
    :return:
    """
    url = f"ws://{getattr(EneGatewayHost, ene)}/ws/webSocket?sn={pile_info[0]}"
    header = [pile_info[1], "Sec-WebSocket-Protocol:ocpp1.6", "Upgrade:websocket"]
    # header = [pile_info[1], "Sec-WebSocket-Protocol:acmp1.0", "Upgrade:websocket"]
    return url, header


def generate_QR_code(in_data, img_file_dir):
    """生成二维码"""
    if not os.path.exists(img_file_dir):
        """如果文件相片不存在才保存,省资源"""
        img = qrcode.make(in_data + "01")
        with open(img_file_dir, 'wb') as f:
            img.save(f)


def get_logger(log_dir):
    logger = logging.getLogger()
    fh = logging.FileHandler(log_dir, encoding='utf-8')
    ch = logging.StreamHandler()
    logging.root.setLevel(logging.NOTSET)
    fh.setLevel(logging.DEBUG)
    ch.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    logger.addHandler(fh)
    logger.addHandler(ch)
    return logger


logger_boj = get_logger(f"./PileLogs/PileLog.log")


def generate_msg(msg_type, requests_type, time_flag=1, pile_connectorId=1, idTag=None, transactionId=None,
                 change_voltage=220, change_electric=32, change_value=None, stop_charging_id=None,
                 Power_Phase=1, gunNO='1', lastTime=None, pileSN=None, seq=None, eventType=1, securityHappen=1,
                 card_id=None, soc_value=None, errorCode=None, vendorErrorCode=None, value=0):
    """
    生成消息数据
    :param vendorErrorCode:
    :param errorCode:
    :param soc_value:           适配MeterValues,传入soc 值
    :param card_id:             适配card_Authorize,刷卡启动的卡的id
    :param securityHappen:      适配SECURITY_EVENT消息
    :param eventType:           适配SECURITY_EVENT消息
    :param seq:                 适配SUSPENDEDEV_TIMEOUT消息,暂停枪序列号,累加
    :param pileSN:              适配SUSPENDEDEV_TIMEOUT消息,暂停枪sn
    :param lastTime:            适配SUSPENDEDEV_TIMEOUT消息,暂停时间
    :param gunNO:               适配SUSPENDEDEV_TIMEOUT消息,暂停枪id
    :param Power_Phase:         电源相位
    :param stop_charging_id:    停止充电id
    :param change_value:        充电电量
    :param change_electric:     充电电流
    :param change_voltage:      充电电压
    :param transactionId:   订单的交易id
    :param idTag:
    :param pile_connectorId: 消息发送给哪个枪
    :param msg_type:        消息类型,
    :param requests_type:   请求类型,一般传2或者3
    :param time_flag:       消息数据结尾的数据
    :return:
    """
    out_data = []
    msg = getattr(Message, msg_type)
    for data in msg:
        out_data.append(data)
    time_info = f"{get_utc0_time()}{time_flag:0>8}"
    if msg_type != "Accepted" and msg_type != "Rejected" and msg_type != "Reject":
        out_data.insert(0, time_info)
        out_data.insert(0, requests_type)
    if msg_type == "Accepted":
        out_data.insert(0, requests_type)
        if idTag is not None:
            out_data.insert(1, idTag)
        if stop_charging_id is not None:
            out_data.insert(1, stop_charging_id)

    if msg_type == "Rejected":
        out_data.insert(0, requests_type)
        if idTag is not None:
            out_data.insert(1, idTag)
        else:
            out_data.insert(1, time_info)

    if msg_type == "Reject":
        out_data.insert(0, requests_type)
        if idTag is not None:
            out_data.insert(1, idTag)
        else:
            out_data.insert(1, time_info)

    if msg_type in ["Available", "Preparing", "Faulted", "Reserved", "StartTransaction", "MeterValues",
                    "SuspendedEVSE", "SuspendedEV", "Finishing", "Unavailable", "Charging"]:
        out_data[-1]["connectorId"] = pile_connectorId

    if msg_type == "Authorize":
        out_data.append({"idTag": idTag})
    elif msg_type == "StartTransaction":  # 开始充电
        out_data[-1]["idTag"] = idTag
        out_data[-1]["timestamp"] = getUTC0()
        out_data[-1]["meterStart"] = f"0.{random.randint(1, 999999)}"
    elif msg_type == "MeterValues":
        out_data[-1]["transactionId"] = transactionId
        out_data[-1]["meterValue"] = [{'sampledValue': [], "timestamp": getUTC0()}]  # 中间
        # out_data[-1]["meterValue"].append({"timestamp": getUTC0()})
        out_data[-1]["meterValue"][0]['sampledValue'].append(
            {'measurand': 'Energy.Active.Import.Register', 'unit': 'Wh', 'value': f"{change_value}.{random.randint(1, 999999)}"})
        for i in range(1, 4):
            out_data[-1]["meterValue"][0]['sampledValue'].append(
                {'measurand': 'Voltage', 'phase': f'L{i}', 'unit': 'V', 'value': f"{change_voltage}.{random.randint(1, 999999)}"})
        for j in range(1, 4):
            out_data[-1]["meterValue"][0]['sampledValue'].append({"value": f"{change_electric}.{random.randint(1, 999999)}", "unit": "A",
                                                                  "measurand": "Current.Export", "phase": f"L{j}"})
        try:
            out_data[-1]["meterValue"][0]['sampledValue'].append(
                {"value": f"{int(change_electric) * int(change_voltage) * int(Power_Phase)}.{random.randint(1, 999999)}", "unit": "W",
                 "measurand": "Power.Offered"})
        except Exception as e:
            print(e, change_electric, change_voltage, Power_Phase)
            out_data[-1]["meterValue"][0]['sampledValue'].append(
                {"value": f"{int(220 * 32 * 3)}.1", "unit": "W",
                 "measurand": "Power.Offered"})
        if soc_value is not None:
            out_data[-1]["meterValue"][0]['sampledValue'].append(
                {"context": "Sample.Periodic", "measurand": "SoC", "value": str(soc_value)})
    elif msg_type == "StopTransaction":
        out_data[-1]["idTag"] = idTag
        out_data[-1]["transactionId"] = transactionId
        out_data[-1]["timestamp"] = getUTC0()
        out_data[-1]["reason"] = "Remote"
        out_data[-1]["meterStop"] = f"{change_value}.{random.randint(1, 999999)}"
    elif msg_type == "SUSPENDEDEV_TIMEOUT":
        out_data[3]['autel']["data"]["gunNO"] = gunNO
        out_data[3]['autel']["data"]['lastTime'] = lastTime
        out_data[3]['autel']["data"]['pileSN'] = pileSN
        out_data[3]['autel']["data"]['transactionId'] = transactionId
        out_data[3]['autel']['seq'] = seq
    elif msg_type == "SECURITY_EVENT":
        out_data[3]['autel']['seq'] = seq
        out_data[3]['autel']['data'][0]['eventType'] = eventType
        out_data[3]['autel']['data'][0]['securityHappen'] = securityHappen
        out_data[3]['autel']['data'][0]['timestamp'] = getUTC0()
    elif msg_type == "POS_RULE":
        out_data[3]['autel']['data']['pileNum'] = pileSN
    elif msg_type == "card_Authorize":
        out_data.append({"idTag": card_id})
    elif msg_type == "card_MeterValues":
        out_data[-1]["connectorId"] = pile_connectorId
        out_data[-1]["idTag"] = card_id
        out_data[-1]["timestamp"] = getUTC0()
    elif msg_type == "Config":
        out_data[-1]['autel']['data']['ChargePoint_SN'] = pileSN
    elif msg_type == "TARIFF_RULE_1":
        out_data[-1]['autel']['ChargePoint_SN'] = pileSN
    elif msg_type == "TARIFF_RULE_2":
        out_data[-1]['autel']['data']['pileNum'] = pileSN
    elif msg_type == "MeterValues_sort":
        out_data[-1]["connectorId"] = pile_connectorId
        out_data[-1]["transactionId"] = transactionId
        out_data[-1]['meterValue'][0]['timestamp'] = getUTC0()
    elif msg_type == "Faulted":
        if errorCode is not None and vendorErrorCode is not None:
            out_data[-1]['errorCode'] = errorCode
            out_data[-1]['vendorErrorCode'] = vendorErrorCode
    elif msg_type == "GET_POWER_TYPE":
        out_data[-1]['autel']["data"]["value"] = value
    return out_data


if __name__ == '__main__':
    import pprint

    # pprint.pprint(generate_msg("Faulted", 2, errorCode="GroundFailure", vendorErrorCode="6"))
    #
    # pprint.pprint(generate_msg("MeterValues", 2, change_value=10,
    #                            transactionId=1111111,
    #                            change_voltage=20, change_electric=30,
    #                            Power_Phase=3))

    # pprint.pprint(generate_msg("Reject", 3, idTag="xxxxxxxxxxxxxxxxxxx"))
    # pprint.pprint(generate_msg("BootNotification", 2))
    #
    # pprint.pprint(generate_msg("MeterValues", 2, change_value=4576, transactionId=11111, change_voltage=1111111,
    #                            Power_Phase=1111111111,
    #                            pile_connectorId=111111))
    #
    pprint.pprint(generate_msg('GET_POWER_TYPE', 2, value=0))

oc_common.py
oc_common.py
BASIC_SALT = ""
firmwareVersion = {
}  # 固件版本号:__UNI__OTA_ECC01/__UNI__OTA_ECP01


class EneGatewayHost:
    china_dev = ""  # 中国开发


class PlatformGatewayObj:
    china_dev_platform = "https://pile-platform-vue-

china_test_mysql = {
                    }  # 中国测试环境数据库配置
china_dev_mysql = {
                   }  # 中国开发环境数据库配置
England_test_mysql = {
                      }  # 英国测试环境数据库配置
mysql_conf = [china_test_mysql, china_dev_mysql, England_test_mysql]
ene_list = ['china_prod', 'china_test', 'china_dev', 'England_test', 'USA_test',
            'USA_prod', 'England_prod', 'Europe_prod', 'ene_wb_dev_cn', "Osi_test"]

sql_sentence = 'SELECT b.pile_sn AS sn,b.control_sn AS c_sn,a.ctrl_pwd AS pwd,a.mac AS mac,' \
               '(SELECT pin FROM pile_device WHERE sn = "xxx") AS pin FROM pile_device AS a INNER JOIN ' \
               'pile_device_relation AS b ON a.sn = b.control_sn HAVING b.pile_sn = "xxx";'


class Message:
    BootNotification = ["BootNotification", {"chargePointModel": "MaxiChargerAC", "chargePointVendor": "Autel",
                                             "firmwareVersion": firmwareVersion[
                                                 "001"]}]  # 引导通知,充电点类型,充电点供应商,固件版本
    Heartbeat = ["Heartbeat", {}]  # 心跳消息
    Available = ["StatusNotification",
                 {"status": "Available", "errorCode": "NoError", "info": "eCp_12V"}]  # 可用状态
    Preparing = ["StatusNotification",
                 {"status": "Preparing", "errorCode": "NoError", "info": "eCp_6V"}]  # 插枪准备状态
    Faulted = ["StatusNotification",
               {"vendorErrorCode": "6,", "status": "Faulted", "errorCode": "GroundFailure",
                "info": "eCp_12V"}]  # 故障状态
    Reserved = ["StatusNotification",
                {"status": "Reserved", "errorCode": "NoError", "info": "eCp_12V"}]  # 预约状态
    SuspendedEVSE = ["StatusNotification",
                     {"status": "SuspendedEVSE", "errorCode": "NoError", "info": "eCp_6V"}]  # 暂停状态
    SuspendedEV = ["StatusNotification",  # SuspendedEV暂停
                   {"status": "SuspendedEV", "errorCode": "NoError", "info": "eCp_6V"}]
    Finishing = ["StatusNotification",
                 {"status": "Finishing", "errorCode": "NoError", "info": "eCp_6V"}]  # Finishing充电完成
    Unavailable = ["StatusNotification",
                   {"status": "Unavailable", "errorCode": "NoError", "info": "eCp_12V"}]  # 不可用
    Charging = ["StatusNotification",
                {"status": "Charging", "errorCode": "NoError", "info": "eCp_6V"}]
    status = [{"status": "Accepted"}]
    # Authorize = ["Authorize", {"idTag": "1531974243087556609"}]
    SUSPENDEDEV_TIMEOUT = ['DataTransfer', {'autel': {'cmd': 'SUSPENDEDEV_TIMEOUT', 'data': {}, },
                                            'data': '', 'vendorId': 'Autel'}]  # EV暂停消息
    SECURITY_EVENT = ["DataTransfer", {"vendorId": "Autel",
                                       "data": "", "autel": {"cmd": "SECURITY_EVENT", "data": [{}]}}]

    # 充电逻辑的数据
    Accepted = [{"status": "Accepted"}]  # 桩发送收到充电了
    Authorize = ["Authorize"]  # 充电消息回复
    card_Authorize = ["Authorize"]
    meterStart = [{"meterStart": 0}]
    MeterValues = ["MeterValues", {}]
    MeterValues_sort = ["MeterValues", {"meterValue": [
        {"sampledValue": [{"value": "0",
                           "context": "Transaction.Begin",
                           "format": "Raw",
                           "measurand": "Energy.Active.Import.Register",
                           "phase": "L3",
                           "location": "Outlet",
                           "unit": "Wh"}]}]}]

    StartTransaction = ["StartTransaction", {"meterStart": 0}]  # 开启充电,加idTag和timestamp
    card_MeterValues = ["StartTransaction", {"meterStart": 0}]
    Rejected = [{"status": "Rejected"}]  # 结束充电
    Reject = [{"status": "Reject"}]  #
    StopTransaction = ["StopTransaction", {}]
    POS_RULE = ["DataTransfer", {"vendorId": "Autel", "data": "", "autel": {"cmd": "POS_RULE", "seq": "10",
                                                                            "data": {"transactionId": 2000056984}}}]
    Config = ["DataTransfer",
              {"vendorId": "Autel", "data": "",
               "autel": {"cmd": "CHARGE_CTRL", "seq": "3449862a-3e54-46f1-b568-e407dbec389a",
                         "data": {"sn": "",
                                  "cpConfig": "V1.05.57,V1.06.50,V00.00.00,V00.00.00,V00.00.00,160,0,,0,0",
                                  "cpSchedule": "0,0,0,0,0,0,0,0",
                                  "pileInfo": {
                                      "version": [{"board": "0", "type": "__UNI__OTA_ECC0101", "ver": "V1.05.57"},
                                                  {"board": "1", "type": "__UNI__OTA_ECP0201", "ver": "V1.06.50"}],
                                      "switchCurrent": "320", "maxCurrentSet": "160", "maxCur": "320", "maxSoc": "0",
                                      "ssid": "TestPile", "startMode": "0", "chargeMode": "0",
                                      "libVer": "10", "sub-G": "0", "subGOn": "0", "rfidOn": "1", "netType": ["WIFI"]},
                                  "support": {"autoResver": "0",
                                              "finishing2pre": "1", "appClearConfig": "1", "restoreSetting": "1"},
                                  "transactionId": 2000034295}}}]
    TARIFF_RULE_1 = ["DataTransfer", {"vendorId": "Autel", "data": "", "autel": {"cmd": "TARIFF_RULE",
                                                                                 "ChargePoint_SN": "",
                                                                                 "data": {"Result": "BusyNow",
                                                                                          "transactionId": 2000034295}}}]
    TARIFF_RULE_2 = ["DataTransfer", {"vendorId": "Autel", "data": "", "autel": {"cmd": "TARIFF_RULE", "seq": "0",
                                                                                 "data": {
                                                                                     "pileNum": "",
                                                                                     "transactionId": 2000034295}}}]
    GET_POWER_TYPE = ["DataTransfer", {"vendorId": "Autel", "data": "",
                                       "autel": {"cmd": "GET_POWER_TYPE", "seq": "1635199972013109249",
                                                 "data": {"connectorId": "1",
                                                          "transactionId": 2000114027}}}]


china_test_bill = {
                   }  # 中国测试环境数据库配置
china_dev_bill = {
                  }  # 中国开发环境数据库配置
England_test_bill = {
                     }  # 英国测试环境数据库配置
mysql_bill_conf = {
    "china_test": china_test_bill,
    "china_dev": china_dev_bill,
    "England_test": England_test_bill
}

user_info = {
    "china_test": {},
    "china_dev": {},
    "England_test": {},
}

if __name__ == '__main__':
    import pprint

    pprint.pprint(getattr(Message, "TARIFF_RULE_2"))

oc_conf.py
oc_conf.py
import threading
import time
import json
import queue

import websocket

from oc_common import get_connect_info, generate_msg, calc_pile_Authorization, logger_boj
from oc_common import generate_QR_code


class OcPile:

    def __init__(self, ene, sn, csn, password, mac, pin):
        self.sn, self.csn, self.password, self.mac, self.pin = sn, csn, password, mac, pin
        self.connect_flag = True  # 桩和服务器的连接状态,默认为连接状态的
        self.autoChangeFlag = False  # 自动充电标志
        self.logObj = logger_boj
        self.card_value = 0
        self.start_time = time.time()
        self.Power_Phase = 3  # 电源相数默认3相
        self.soc_rate = 0  # soc增长比例是0,表示不传soc
        self.soc_value = 0  # soc当前值,默认为0
        self.card_Charging_flag = False  # 默认没有刷卡充电,刷卡充电的标签
        self.Pause_charging_flag = None  # 暂停充电标志,默认None正常充电
        self.time_flag = 1  # 发送消息的唯一标识
        self.requests_type2 = 2  # 请求数据类型
        self.requests_type3 = 3  # 请求数据类型
        self.idTag_queue = queue.Queue()  # 充电id
        self.stop_charging_id_queue = queue.Queue()  # 停止充电的id标志
        self.change_flag = False  # 充电状态标志,默认没有充电
        self.transactionId_queue = queue.Queue()  # 交易id
        self.now_transactionId_lis = []  # 正在交易的订单,存订单id,收到开启通知放这里,停止充电拿出去
        self.change_electric = 32  # 充电电流
        self.change_voltage = 220  # 充电电压
        self.change_energy = int(self.change_electric) * int(self.change_voltage) * int(self.Power_Phase)
        if self.sn[0:2] == "AE":
            self.oc_powerSystem = '0'  # 电源系统设置默认 为0 TN/TT供电系统,1为IT供电系
        self.pile_state = "heartbeat"  # 默认桩状态正常
        self.au = calc_pile_Authorization(self.csn, self.password, self.mac)
        generate_QR_code(self.sn, f"./PilePicDir/{self.sn}.png")
        self.url, self.header = get_connect_info(ene, (self.sn, self.au))
        self.ws = websocket.WebSocket()
        self.ws.connect(self.url, header=self.header)
        print(self.header, self.url)
        threading.Thread(target=self.oc_while_heartbeat).start()
        threading.Thread(target=self.oc_while_recv).start()
        self.oc_pile_charging_Preparing()  # 插枪准备充电状态

    def oc_pile_charging_Preparing(self):
        """枪处于准备状态,插枪"""
        self.oc_send_message("BootNotification", self.requests_type2)
        time.sleep(1)
        self.oc_send_message("Available", self.requests_type2, pile_connectorId=0)
        self.oc_send_message("Preparing", self.requests_type2)
        self.pile_state = "Preparing"
        if self.sn[0] == "D":
            # 如果是直流桩,那么就把枪2置于插枪
            self.oc_send_message("Preparing", self.requests_type2, pile_connectorId=2)

    def oc_change_pile_state(self, state):
        """切换桩的状态"""
        self.oc_send_message(state, self.requests_type2)
        self.pile_state = state
        print("切换成功")

    def oc_pile_start_change(self):
        """桩主动开启充电,定时充电"""
        self.oc_send_message("StartTransaction", self.requests_type2, idTag="01AUTEL0000000000003")
        self.autoChangeFlag = True
        threading.Thread(target=self.oc_pile_changing).start()

    def oc_pile_changing(self):
        """桩定时充电的逻辑,只要autoChangeFlag的标志为True,就一直充电"""
        value = 0
        # 开启充电后需要得到一个 transactionId  拿到
        transactionId = self.transactionId_queue.get()
        self.oc_send_message("Charging", self.requests_type2, pile_connectorId=1)  # 发送桩状态充电中
        self.oc_send_message("MeterValues_sort", self.requests_type2, transactionId=transactionId)  # 发送充电消息
        while self.autoChangeFlag:
            self.oc_send_message("MeterValues", self.requests_type2, change_value=str(value),
                                 transactionId=transactionId,
                                 change_voltage=self.change_voltage, change_electric=self.change_electric,
                                 Power_Phase=self.Power_Phase)
            time.sleep(10)
            value += int(round(self.change_energy / 3600 * 10 * self.Power_Phase, 1))
        self.oc_send_message("StopTransaction", self.requests_type2, idTag="01AUTEL0000000000003",
                             transactionId=transactionId,
                             change_value=str(value))
        time.sleep(0.1)
        self.oc_send_message("Finishing", self.requests_type2)

    def oc_pile_stop_change(self):
        """桩主动停止定时充电"""
        self.autoChangeFlag = False

    def oc_changer_ing(self, pile_connectorId=1):
        """充电"""
        value = 0
        lastTime = 1780  # 暂停充电时间
        seq = 0  # 暂停充电序列号
        idTag = self.idTag_queue.get()
        self.oc_send_message("Accepted", self.requests_type3, idTag=idTag)
        time.sleep(0.1)
        self.oc_send_message("Authorize", self.requests_type2, idTag=idTag)
        time.sleep(0.1)
        self.oc_send_message("Charging", self.requests_type2, pile_connectorId=pile_connectorId)  # 枪1发送充电中的消息
        time.sleep(0.1)
        self.oc_send_message("StartTransaction", self.requests_type2, idTag=idTag, pile_connectorId=pile_connectorId)
        print("开启充电结束了,循环发送:meterValue")
        transactionId = self.transactionId_queue.get()
        while 1:
            if self.Pause_charging_flag is None and self.soc_rate < 99 and self.connect_flag:
                # 如果暂停充电的flag是None,soc的值小于99,并且self.connect_flag连接标志为True,那么一直充电
                try:
                    if transactionId not in self.now_transactionId_lis:
                        # 当transactionId在交易列表的时候表示还在充电中,不在了表示交易结束收到了结束通知,调用充电逻辑
                        stop_charging_id = self.stop_charging_id_queue.get()
                        print("充电结束了,调用充电逻辑")
                        # time.sleep(10)
                        self.oc_send_message("Rejected", self.requests_type3)
                        time.sleep(0.1)
                        self.oc_send_message("Accepted", self.requests_type3, stop_charging_id=stop_charging_id)
                        time.sleep(0.1)
                        self.oc_send_message("Finishing", self.requests_type2, pile_connectorId=pile_connectorId)
                        time.sleep(0.1)
                        self.oc_send_message("Preparing", self.requests_type2, pile_connectorId=pile_connectorId)
                        time.sleep(0.1)
                        self.oc_send_message("StopTransaction", self.requests_type2, idTag=idTag,
                                             transactionId=transactionId,
                                             change_value=str(value))  # 充电结束后发送结束信息,meterStop本次充电耗费了多少电量
                        break
                    if self.soc_rate == 0:  # 如果soc的增长率为0,表示不传soc值
                        self.oc_send_message("MeterValues", self.requests_type2, change_value=str(value),
                                             transactionId=transactionId, change_voltage=self.change_voltage,
                                             change_electric=self.change_electric, Power_Phase=self.Power_Phase,
                                             pile_connectorId=pile_connectorId)
                        print(f"MeterValues发送成功:电流{self.change_electric}:电压{self.change_voltage}:电量{str(value)}")

                    elif self.soc_rate > 0:  # 如果soc的增长率为0,表示不传soc值
                        self.oc_send_message("MeterValues", self.requests_type2, change_value=str(value),
                                             transactionId=transactionId,
                                             change_voltage=self.change_voltage, change_electric=self.change_electric,
                                             Power_Phase=self.Power_Phase, soc_value=self.soc_value,
                                             pile_connectorId=pile_connectorId)
                        print(f"MeterValues发送成功:电流{self.change_electric}:电压{self.change_voltage}:"
                              f"电量{str(value)}:soc{self.soc_rate}")
                        self.soc_value += self.soc_rate
                    time.sleep(5)
                    value += int(round(self.change_energy / 3600 * 5 * self.Power_Phase, 1))
                except ConnectionResetError as e:  # 如果充电过程中断连了,发送消息出错了
                    self.connect_flag = False
                    # 如果充电过程中断连了,那么就把连接状态置为False,这里不尝试重连,交给心跳线程去重连
                    print(f"changer_ing:{e}")
                    self.logObj.info(f"充电过程中桩断连了,错误信息为:{e},当前未结束的订单为:{transactionId}")
            elif self.soc_rate > 99:
                print("充电桩充满了,此时暂停充电不发生电流和电压了")
            elif self.Pause_charging_flag:  # 如果暂停充电的flag是True,那么停止充电,切换桩状态,并且发送暂停消息
                if not self.change_flag:
                    stop_charging_id = self.stop_charging_id_queue.get()
                    print("充电结束了,调用充电逻辑")
                    # time.sleep(10)
                    self.oc_send_message("Rejected", self.requests_type3)
                    time.sleep(0.1)
                    self.oc_send_message("Accepted", self.requests_type3, stop_charging_id=stop_charging_id)
                    time.sleep(0.1)
                    self.oc_send_message("Finishing", self.requests_type2, pile_connectorId=pile_connectorId)
                    time.sleep(0.1)
                    self.oc_send_message("Preparing", self.requests_type2, pile_connectorId=pile_connectorId)
                    time.sleep(0.1)
                    self.oc_send_message("StopTransaction", self.requests_type2, idTag=idTag,
                                         transactionId=transactionId,
                                         change_value=str(value))  # 充电结束后发送结束信息,meterStop本次充电耗费了多少电量
                    break
                time.sleep(3)
                lastTime += 3
                seq += 1
                # self.send_message("SuspendedEV", self.requests_type2, pile_connectorId=1)  # 发送当前枪状态暂停
                self.oc_send_message("SUSPENDEDEV_TIMEOUT", self.requests_type2, gunNO='1', lastTime=str(lastTime),
                                     pileSN=self.sn, transactionId=transactionId, seq=seq,
                                     pile_connectorId=pile_connectorId)
                self.oc_send_message("MeterValues", self.requests_type2, change_value=str(value),
                                     transactionId=transactionId,
                                     change_voltage=self.change_voltage, change_electric=0,
                                     Power_Phase=self.Power_Phase, pile_connectorId=pile_connectorId)
            elif self.Pause_charging_flag is False:  # 如果暂停充电的flag是False,表示取消了暂停,切换桩的状态为charing,把flag置为None
                self.oc_send_message("Charging", self.requests_type2, pile_connectorId=pile_connectorId)  # 枪1发送充电中的消息
                self.Pause_charging_flag = None
                lastTime = 1780
                seq = 0
            elif not self.connect_flag:
                """如果充电的时候连接状态断开了,啥也不干,等10s,等心跳线程和recv线程重连就行了"""
                time.sleep(10)

    def oc_card_Charging(self, card):
        """刷卡启动后频繁发送消息"""
        value = 0
        self.oc_send_message("Charging", self.requests_type2)  # 发送充电状态
        time.sleep(3)
        self.oc_send_message("card_MeterValues", 2, card_id=card)  # 发送起始的MeterValues
        self.card_transactionId = self.transactionId_queue.get()
        while self.card_Charging_flag:  # 如果self.card_Charging_flag标志是True,那么就一直充电
            print(f"刷卡充电的MeterValues发送成功")
            self.oc_send_message("MeterValues", self.requests_type2, change_value=str(value),
                                 transactionId=self.card_transactionId,
                                 change_voltage=self.change_voltage, change_electric=self.change_electric,
                                 Power_Phase=self.Power_Phase)
            time.sleep(5)
            value += int(round(self.change_energy / 3600 * 5 * self.Power_Phase, 1))
        self.oc_send_message("StopTransaction", self.requests_type2, idTag=card, transactionId=self.card_transactionId,
                             change_value=str(value))  # 刷卡结束后发送的消息
        self.oc_send_message("Rejected", self.requests_type3)
        time.sleep(1)
        self.oc_send_message("Finishing", self.requests_type2)
        time.sleep(1)
        self.oc_send_message("Preparing", self.requests_type2)

    def oc_send_message(self, msg_type, requests_type, *args, **kwargs):
        """发送消息"""
        message = json.dumps(generate_msg(msg_type, requests_type, self.time_flag, *args, **kwargs))
        if msg_type == "Faulted":
            print(message)
        self.ws.send(message)
        self.time_flag += 1
        self.logObj.info(f"{self.sn}:SEND:{message}")

    def oc_send_heartbeat(self):
        self.oc_send_message("Heartbeat", self.requests_type2)

    def oc_while_heartbeat(self):
        """心跳线程,一直发心跳,死循环"""
        while 1:
            if self.connect_flag:  # 当连接状态为True的时候,可以一直发心跳
                try:
                    self.oc_send_heartbeat()
                    time.sleep(10)
                except Exception as e:  # 当发送消息失败的时候
                    print(f"心跳线程发送消息出错了,错误信息为:{e}")
                    self.connect_flag = False  # 发送消息发生错误了就把连接状态置为False
            elif not self.connect_flag:  # 连接状态为False了那么需要重新连接服务器
                try:
                    self.ws.connect(self.url, header=self.header)
                    self.connect_flag = True  # 如果重连成功了,那么连接状态置为True
                    if self.change_flag:  # 如果桩处于充电中,重连成功后枪状态置为  Charging
                        self.oc_send_message("Charging", self.requests_type2, pile_connectorId=1)
                    else:  # 正常情况重连成功了桩置为  Preparing状态
                        self.oc_send_message("Preparing", self.requests_type2)  # 重连成功了的话那么就把桩的状态置为Preparing
                except Exception as e:
                    print(f"重连失败了,连接失败的信息为:{e}")
                    time.sleep(10)  # 10s后继续重连服务器

    def oc_recv_msg(self):
        recv_message = self.ws.recv()
        self.logObj.info(f"{self.sn}:RECV:{recv_message}")
        return recv_message

    def oc_while_recv(self):
        """循环接受消息线程,一直收,死循环"""
        while 1:  #
            if self.connect_flag:  # 当连接状态为True的时候,可以一直收消息,进行后面的逻辑判断
                try:
                    recv_message = self.oc_recv_msg()  # 正常接收到的消息
                except Exception as e:
                    recv_message = ""  # 如果异常里的话定义recv_message为空,并且把self.connect_flag置为False
                    self.connect_flag = False
                    print(f"当前接收消息协程报错,报错信息为:{e}")
                    # raise e

                if recv_message.__contains__("RemoteStartTransaction"):
                    self.change_flag = True  # 开始充电标识
                    try:
                        self.idTag_queue.put(json.loads(recv_message)[-1]["idTag"])
                        connect_id = json.loads(recv_message)[-1]["connectorId"]
                    except Exception as e:
                        connect_id = 1
                        print(e, "00000000000000000000000000000000000000000000")
                    threading.Thread(target=self.oc_changer_ing, args=(connect_id,)).start()  # 调用充电线程

                elif recv_message.__contains__("RemoteStopTransaction"):
                    self.change_flag = False  # 充电结束标识
                    try:
                        self.stop_charging_id_queue.put(json.loads(recv_message)[1])
                        self.now_transactionId_lis.remove(json.loads(recv_message)[-1]["transactionId"])
                        # 交易完成后吧交易id从列表拿出去,表示这个不交易了
                    except Exception as e:
                        print(e, "1111111111111111111111111111111111111")

                elif recv_message.__contains__("transactionId") and recv_message.__contains__('parentIdTag'):
                    # 获取交易id
                    try:
                        now_transactionId = json.loads(recv_message)[-1]["transactionId"]
                        self.transactionId_queue.put(now_transactionId)
                        self.now_transactionId_lis.append(now_transactionId)  # 拿到交易id的时候存在这里,表示当前交易的订单
                        idTag = json.loads(recv_message)[1]
                        self.oc_send_message("Accepted", self.requests_type3, idTag=idTag)
                    except Exception as e:
                        print(e, recv_message)
                elif recv_message.__contains__('CHARGE_AMOUNT'):
                    try:
                        idTag = json.loads(recv_message)[1]
                        self.oc_send_message("Accepted", self.requests_type3, idTag=idTag)
                    except Exception as e:
                        print(e, recv_message)
                elif recv_message.__contains__('SET_POWER_SUPPLY'):
                    try:
                        recv_message = json.loads(recv_message)
                        print(recv_message, 'xxxxxxxxxxxxx')
                        idTag = recv_message[1]
                        self.oc_powerSystem = recv_message[3]['autel']['data']['value']
                        self.oc_send_message("Accepted", self.requests_type3, idTag=idTag)
                        # self.oc_send_message("Rejected", self.requests_type3, idTag=idTag)
                        print(self.oc_powerSystem)
                    except Exception as e:
                        print(e, recv_message)
                        raise e
                elif recv_message.__contains__('GET_POWER_TYPE'):
                    try:
                        idTag = json.loads(recv_message)[1]
                        self.oc_send_message("Accepted", self.requests_type3, idTag=idTag)
                        time.sleep(0.1)
                        self.oc_send_message("GET_POWER_TYPE", self.requests_type2, value=self.oc_powerSystem)
                    except Exception as e:
                        print(e, recv_message)
                elif recv_message.__contains__('GetConfiguration'):         #
                    try:
                        idTag = json.loads(recv_message)[1]
                        self.oc_send_message("Accepted", self.requests_type3, idTag=idTag)
                        time.sleep(0.1)
                        self.oc_send_message("GET_POWER_TYPE", self.requests_type2, value=self.oc_powerSystem)
                    except Exception as e:
                        print(e, recv_message)
            elif not self.connect_flag:  # 如果连接状态为False,表示断连了,啥也不干,等待心跳线程重连,这里继续等待就行
                time.sleep(10)

oc_pile.py
oc_pile.py
import os
import sys
from LoginPage import Tk, LoginPage

current_dir = os.getcwd()
sys.path.insert(0, current_dir)

if not os.path.exists("./PilePicDir"):
    os.mkdir("./PilePicDir")
if not os.path.exists("./PileLogs"):
    os.mkdir("./PileLogs")

root = Tk()
root.title('输入充电桩信息')
LoginPage(root)
root.mainloop()

startPile.py
startPile.py

 24:WebSocket长链接性能压测脚本编写思路【Locust+Python】

from gevent import monkey;monkey.patch_all()

from ac_common import *

class AcmpMessage:
    def action_pushrecord(self, rcType, seq, unitId, data):
        pushrecord = {
            "action": "PushRecord",
            "seq": seq,
            "payload": {
                "msgId": "AL0038B1GN7CTEST01:63733760:174",
                "rcData": [
                    {
                        "unitId": unitId,
                        "rcType": rcType,
                        "rcVer": 0,
                        "data": data
                    }
                ]
            }
        }
        return pushrecord

    def action_upMessage(self, seq, msgId, srcAddr, frameId, optType, data):
        upMessage = {
            "action": "UpMessage",
            "seq": seq,
            "payload": {
                "msgId": msgId,
                "msgData": [
                    {
                        "msgHeader": {
                            "srcAddr": srcAddr,
                            "dstAddr": 1,
                            "frameId": frameId,  # 帧序号,每次发送递增
                            "optType": optType,
                            "optVer": 0,
                            "crcSum": generate_crcSum(data)
                        },
                        "time": get_utc_time(),
                        "data": base64_encode(data)
                    }
                ]
            }
        }
        return upMessage

    def resp_dnMessage(self, seq, msgId, status, srcAddr, dstAddr, frameId, optType, data):
        t = datetime.now().timestamp()
        utc_time = datetime.utcfromtimestamp(t).isoformat()  # UTC时间
        utc_time = utc_time.split(".")[0]
        crcSum = generate_crcSum(base64_decode_list(data))
        resp = {
            "resp": "DnMessage",
            "seq": seq,
            "status": status,
            "payload": {
                "msgId": msgId,
                "status": status,
                "msgData": [
                    {
                        "msgHeader": {
                            "srcAddr": srcAddr,
                            "dstAddr": dstAddr,
                            "frameId": frameId,
                            "optType": optType,
                            "optVer": 0,
                            "crcSum": crcSum
                        },
                        "time": utc_time,
                        "data": data
                    }
                ]
            }
        }
        return resp

    def update_firmware(self, seq, status):
        resp = {
            "resp": "UpdateFirmware",
            "seq": seq,
            "status": status,
            "payload": {
                "status": status
            }
        }
        return resp

ac_action.py
ac_action.py
from gevent import monkey;monkey.patch_all()
import json

from datetime import datetime, timedelta
import requests
import pymysql
import base64
import hashlib

from ac_config import EneGatewayHost, sql_sentence, mysql_conf, BASIC_SALT


def get_mysql_conn(mysql_data):
    """获取mysql游标"""
    conn = pymysql.connect(
        host=mysql_data["host"],
        port=mysql_data["port"],
        user=mysql_data["user"],
        password=mysql_data["password"],
        db=mysql_data["database"],
        charset=mysql_data["charset"])
    cursor = conn.cursor()
    return cursor, conn


def get_pile_info(pile_sn):
    """获取桩的信息"""
    execute_sql = sql_sentence.replace("xxx", pile_sn)
    for mq in mysql_conf:
        cursor, conn = get_mysql_conn(mq)
        cursor.execute(execute_sql)
        res = cursor.fetchone()
        if res:
            return res
    else:
        return False


def calc_pile_Authorization(ctrlSn, pwd, mac):
    """生成鉴权码"""
    sha256 = hashlib.sha256()
    sha256.update(f"{ctrlSn}:{pwd}:{BASIC_SALT}:{mac.replace('-', ':').lower()}".encode('utf-8'))
    in_data = sha256.digest()
    res = 'Authorization:Basic ' + str(base64.b64encode((ctrlSn + ":" + base64.b64encode(in_data).decode("utf-8")).
                                                        encode('utf-8')).decode('utf-8'))
    return res


def get_connect_info(ene, pile_info):
    """
    获取连接信息,返回连接的url和header
    :param ene:         环境(china_dev,china_test,china_prod,England_test,USA_test,USA_prod,England_prod,Europe_prod)
    :param pile_info:   桩信息
    :return:
    """
    url = f"ws://{getattr(EneGatewayHost, ene)}/ws/websocket?sn={pile_info[0]}"
    ocpp_header = [pile_info[1], "Sec-WebSocket-Protocol:ocpp1.6", "Upgrade:websocket"]
    acmp_header = [pile_info[1], "Sec-WebSocket-Protocol:acmp1.0", "Upgrade:websocket"]
    return url, ocpp_header, acmp_header


def base64_encode(data):
    """base64对列表編碼"""
    if isinstance(data, list):
        bytes_data = bytes(data)  # 轉化成字節串
        base64_data = str(base64.b64encode(bytes_data), 'utf-8')
        return base64_data
    elif isinstance(data, str):
        base64_data = str(base64.b64encode(data.encode("utf-8")), "utf-8")
        return base64_data
    elif isinstance(data, dict):
        str_data = json.dumps(data)
        base64_data = str(base64.b64encode(str_data.encode('utf-8')), 'utf-8')
        return base64_data

    else:
        print("未知类型")


def base64_decode_list(data):
    """对经过base64编码的字节数组解密,返回列表"""
    li = list(base64.b64decode(data))
    # print("解密後:", n)
    return li


def base64_decode_json(data):
    """对经过base64编码的json数据密,返回字典"""
    data = json.loads(base64.b64decode(data))
    return data


def base64_decode_str(data):
    """对经过base64编码的数据解密,返回字符串"""
    data = str(base64.b64decode(data),'utf-8')
    return data


def get_utc_time():
    t = datetime.now().timestamp()
    utc_time = datetime.utcfromtimestamp(t).isoformat()  # UTC时间
    utc_time = utc_time.split(".")[0]
    return utc_time


def sixteen_to_ten(number):
    """16进制转10进制,入参需传字符串类型"""
    return int(number, 16)


def generate_time():
    """获取utc时间转成列表"""
    data_time = []  # 發生時間,第5到12位
    now_date = datetime.now()  # 獲取當前時間
    utc_time = now_date - timedelta(hours=8)  # 转成utc时间
    utc_time = str(utc_time)
    print(utc_time)
    data_time.append(int(utc_time[2:4]))
    data_time.append(int(utc_time[5:7]))
    data_time.append(int(utc_time[8:10]))
    data_time.append(int(utc_time[11:13]))
    data_time.append(int(utc_time[14:16]))
    data_time.append(int(utc_time[17:19]))
    data_time.append(int(utc_time[20:22]))
    data_time.append(0)
    return data_time


def generate_crcSum(data_list):
    crcSum = 0
    for i in data_list:
        crcSum += i
    return crcSum


def generate_businessType(data_list):
    data_list = base64_decode_list(data_list)
    businessType = data_list[0:2]
    return businessType


def event_data_encode(data):
    lis = [26, 0, 0, 0, 22, 11, 23, 7, 2, 17, 0, 0, 1, 0, 2, 117, 0]
    data = data
    data = base64_encode(data)
    data = base64_decode_list(data)
    data = lis + data
    data = base64_encode(data)
    print(data)


def event_data_decode(data):
    data = base64_decode_list(data)
    data_id = data[0:4]
    event_time = data[4:12]
    event_code = data[12:14]
    param_type = data[14:15]
    param_len = data[15:17]
    event_param = data[17:]
    event_param = base64_decode_str(base64_encode(event_param))
    print(event_param)


def download_firmware(url):
    url = url
    response = requests.get(url=url)
    with open('./test.aut', 'wb') as f:
        f.write(response.content)


def md5(data):
    m = hashlib.md5()
    m.update(data.encode(encoding='utf-8'))
    data_md5 = m.hexdigest()
    return data_md5


def get_token(ene, account, password):
    login_url = ene + "/api/saas-access-app/portal/login"
    data = {
        "account": account,
        "password": md5(password)
    }
    response = requests.post(url=login_url, json=data)
    token = response.json()['data']['token']
    return token


if __name__ == '__main__':
    le = base64_decode_list('AQ==')
    print(le)

ac_common.py
ac_common.py
from gevent import monkey;monkey.patch_all()


BASIC_SALT = ""
firmwareVersion = {
    }  # 固件版本号:__UNI__OTA_ECC01/__UNI__OTA_ECP01


class EneGatewayHost:
    china_dev = ""  # 中国开发
    china_test = ""  # 中国测试
    china_prod = "  # 中国生产
    England_test = ""  # 英国测试
    USA_test = ""  # 美西测试
    USA_prod = ""  # 美国生产
    England_prod = ""  # 英国生产
    Europe_prod = ""  # 欧洲生产
    Canada_prod = ""  # 加拿大生产
    ene_wb_dev_cn = ""  # 联调环境


class EneLoginHost:
    china_dev = ""  # 中国开发
    china_test = ""  # 中国测试
    china_prod = ""  # 中国生产
    England_test = ""  # 英国测试
    USA_test = ""  # 美西测试
    USA_prod = ""  # 美国生产
    England_prod = ""  # 英国生产
    Europe_prod = ""  # 欧洲生产
    Canada_prod = ""  # 加拿大生产
    ene_wb_dev_cn = ""  # 联调环境


class PlatformGatewayObj:
    china_test_platform = ""
    china_dev_platform = ""
    England_test_platform = ""


china_test_mysql = {
                    }  # 中国测试环境数据库配置
china_dev_mysql = {
                   }  # 中国开发环境数据库配置
England_test_mysql = {
                      }  # 英国测试环境数据库配置
mysql_conf = [china_test_mysql, china_dev_mysql, England_test_mysql]
ene_list = ['china_prod', 'china_test', 'china_dev', 'England_test', 'USA_test',
            'USA_prod', 'England_prod', 'Europe_prod', 'ene_wb_dev_cn']

sql_sentence = 'SELECT b.pile_sn AS sn,b.control_sn AS c_sn,a.ctrl_pwd AS pwd,a.mac AS mac,' \
               '(SELECT pin FROM pile_device WHERE sn = "xxx") AS pin FROM pile_device AS a INNER JOIN ' \
               'pile_device_relation AS b ON a.sn = b.control_sn HAVING b.pile_sn = "xxx";'


china_test_bill = {
                   }  # 中国测试环境数据库配置
china_dev_bill = {
                  }  # 中国开发环境数据库配置
England_test_bill = {
                     }  # 英国测试环境数据库配置
mysql_bill_conf = {
    "china_test": china_test_bill,
    "china_dev": china_dev_bill,
    "England_test": England_test_bill
}

user_info = {
    "china_test": {},
    "china_dev": {},
    "England_test": {},
}

ac_config.py
ac_config.py
from gevent import monkey;monkey.patch_all()
import sqlite3

import random
import string

from ac_action import AcmpMessage
from ac_common import *


class CcuMessage(AcmpMessage):

    def ccu_work_status(self, seq, srcAddr, frameId):
        """8.5.1.1,CCU_主动上报工作状态数据,业务类型为0x0400"""
        work_status_list = [0, 1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150]
        work_status = random.choice(work_status_list)
        # pile_status = [0]
        # status = [0]
        # reserved = [0]
        memory_usage = random.randint(0, 100)
        maximum_task_stack_usage = random.randint(0, 100)
        maximum_task_stack = [84, 101, 115, 116, 84, 101, 115, 116, 84, 101, 115, 116, 84, 101, 115, 116, ]
        data = [work_status, 0, 0, 0, memory_usage, maximum_task_stack_usage] + maximum_task_stack
        optType = 1024
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage

    def ccu_telemetry_data(self, seq, srcAddr, frameId):
        """CCU_主动上报遥测数据,业务类型为0x0401"""
        data = [189, 4, 232, 3, 0, 0, 1, 0, 103, 9, 0, 0, 0, 0, 50, 0, 78, 50, 249, 255, 0, 0, 0, 0, 20, 5, 0, 0, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        optType = 1025
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage

    def ccu_telemetry_charging_process(self, seq, srcAddr, frameId):
        """CCU_主动上报遥测(充电过程)数据,业务类型为0x0402"""
        data = [251, 255, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        optType = 1026
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage

    def ccu_telesignalling_data(self, seq, srcAddr, frameId):
        """CCU_主动上报遥信数据,业务类型为0x0403"""
        data = [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        optType = 1027
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage


class EcuMessage(AcmpMessage):
    def ecu_work_status(self, seq, frameId):
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 96, 96, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        message = self.action_upMessage(seq, msgId, 30, frameId, 1280, data)
        return message

    def ecu_telemetry_data(self, seq, frameId):
        """ECU_主动上报遥测数据,业务类型为0x0501"""
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [0, 0, 26, 0, 28, 0, 27, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        message = self.action_upMessage(seq, msgId, 30, frameId, 1281, data)
        return message

    def ecu_telesignalling_data(self, seq, frameId):
        """ECU_主动上报遥信数据,业务类型为0x0502"""
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        message = self.action_upMessage(seq, msgId, 30, frameId, 1282, data)
        return message

    def ecu_charging_data(self, seq, frameId):
        """ECU_主动上报充电模块数据,业务类型0x0503"""
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [1, 0, 0, 16, 40, 108, 40, 40, 40, 149, 156, 6, 255, 50, 49, 48, 50, 51, 49, 50, 86, 71, 81, 75, 88, 0, 1, 9, 1, 9, 1, 1, 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, 139, 19, 133, 0, 100, 0, 239, 15, 217, 15, 3, 16, 0, 0, 31, 0, 27, 0, 0, 0, 0, 0, 0, 64, 0, 0, 2, 0, 0, 132, 0, 0, 0, 0, 0, 0, 0]
        message = self.action_upMessage(seq, msgId, 30, frameId, 1283, data)
        return message


class DtmMessage(AcmpMessage):

    def dtm_work_status(self, seq, frameId):
        """8.7.1.1 DTM_工作状态上报数据,业务类型为0x0600"""
        data = [0, 0, 0, 0, 0, 0, 0, 2, 28, 0, 0, 0, 4, 0, 0, 0, 5]
        optType = 1536
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        message = self.action_upMessage(seq, msgId, 10, frameId, optType, data)
        return message

    def pile_realtime_status(self, seq, frameId, gunNo, status):
        """8.7.1.2  DTM_充电接口相关实时数据上报,业务类型为0x0601"""
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        data = [gunNo, status, 0, 0, 48, 1, 36, 0, 0, 0, 3, 0, 0, 2, 0, 0, 0, 0, 0, 1]
        message = self.action_upMessage(seq, msgId, 10, frameId, 1537, data)
        return message

    def upgrade_firmware_status(self, seq, frameId, upgrade_status):
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        srcAddr = 10
        optType = 1584
        data = [upgrade_status, 10]  # 第二位总体进度
        message = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return message


class AcuMessage(AcmpMessage):

    def acu_work_status(self, seq, srcAddr, frameId):
        """8.9.1.1  ACU_主动上报工作状态数据,业务类型为0x0700"""
        work_status_list = [0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
        work_status = random.choice(work_status_list)  # 工作状态
        pile_status = 0  # 充电桩状态 00H:车辆未连接   01H:车辆已连接,02H:参数握手阶段,03H:绝缘监测阶段,04H:参数辨识阶段,05H:参数配置阶段,06H:预充电阶段,
        # 07H:正式充电阶段,08H:充电暂停,09H:充电停止中 ,0AH:充电停止完成,0BH:充电完成
        vehicle_connection_status = 1  # 车辆连接状态,0:已连接  1:未连接
        cp_status = 0  # CP状态,0x00:CP异常,0x01H:12V,0x02H:9V <br /> 0x03H:6V,0x04H:接地
        pp_status = 0  # PP状态,0x00H:断开,0x01H:13A,0x02H:20A,0x03H:32A,0x04H:63A,0x05H:异常
        electronic_lock_condition = 0  # 电子锁状态,00H:解锁状态,01H:上锁状态,02H:异常状态
        contactor_status = 0  # 接触器状态,00H:断开,01H:闭合
        power_off_condition = 0  # 掉电状态,00H:掉电状态,01H:上电状态
        memory_usage = random.randint(0, 100)  # 系统动态内存使用百分比
        maximum_task_stack_usage = random.randint(0, 100)  # 任务堆栈最大使用百分比
        maximum_task_stack = [84, 101, 115, 116, 84, 101, 115, 116, 84, 101, 115, 116, 84, 101, 115,
                              116, ]  # 任务名字,ASCII,16
        data = [work_status, pile_status, vehicle_connection_status, cp_status, pp_status, electronic_lock_condition,
                contactor_status, power_off_condition, memory_usage, maximum_task_stack_usage] + maximum_task_stack
        msgId = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        optType = 1792
        utc_time = get_utc_time()
        upmessage = self.action_upMessage(seq, msgId, srcAddr, frameId, optType, data)
        return upmessage

    def acu_telemetry_data(self, seq, srcAddr, frameId):
        pass

    def acu_signaling_data(self):
        pass


class ResponseMessage(AcmpMessage):
    def __init__(self):
        self.conn = sqlite3.connect('test.db')
        self.cursor = self.conn.cursor()

    def sel_local_start(self, seq, frameId, msgId):
        """查询本地充电应答"""
        status = "Accept"
        srcAddr = "10"
        dstAddr = 1
        optType = 387
        GetLocalStartReply = 2
        data = f'{{"code":26,"dataObj":{{"GetLocalStartReply":{GetLocalStartReply}}},"statusType":"accept"}}'
        # 0云端允许,桩允许、1云端允许,桩禁止、2云端禁止,桩禁止
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_local_start(self, seq, frameId, msgId):
        """设置本地充电应答"""
        status = "Accept"
        srcAddr = "10"
        dstAddr = 1
        optType = 387
        data = self.cursor.execute(("SELECT value FROM message_config where name = '设置本地充电应答';"))
        data = data.fetchone()
        data = data[0]
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def sel_upload_frequency(self, seq, frameId, msgId, srcAddr, businessType):
        """查询数据上传频率应答"""
        status = "Accept"
        srcAddr = srcAddr
        dstAddr = 1
        optType = 305
        data = [0]
        data = data + businessType + [6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_upload_frequency(self, seq, frameId, msgId, srcAddr, businessType):
        """设置数据上传频率应答"""
        status = "Accept"
        srcAddr = srcAddr
        dstAddr = 1
        optType = 307
        data = [0]
        data = data + businessType + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def sel_log_level(self, seq, frameId, msgId, srcAddr):
        """查询日志上传等级应答"""
        status = "Accept"
        srcAddr = srcAddr
        dstAddr = 1
        optType = 373
        data = [4]  # 1-fault 2-warning 3-info 4-debug
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_log_level(self, seq, frameId, msgId, srcAddr):
        status = "Accept"
        """设置日志上传等级应答"""
        srcAddr = srcAddr
        dstAddr = 1
        optType = 370
        data = [0]  # 0:表示成功;  1:表示失败
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_silent_mode(self, seq, frameId, msgId):
        """设置静音模式"""
        status = "Accept"
        srcAddr = 30
        dstAddr = 1
        optType = 1305
        data = [0]  # 0表示成功;  其它值表示失败原因
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def disable_pile(self, seq, frameId, msgId, gunNo):
        """禁用充电枪"""
        status = "Accept"
        srcAddr = 10
        dstAddr = 1
        optType = 1571
        data = [gunNo, 0, 48, 85, 58, 124, 0, 0, 0, 32]  # 0表示成功;  其它值表示失败原因
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message

    def set_time_zone(self, seq, frameId, msgId):
        """设置时区"""
        status = "Accept"
        srcAddr = "10"
        dstAddr = 1
        optType = 387
        data = '{"code":30,"statusType":"accept"}'
        data = base64_encode(data)
        response_message = self.resp_dnMessage(seq, msgId, status, srcAddr, dstAddr, frameId, optType, data)
        return response_message


class ReportEvent(AcmpMessage):

    def report_alert(self, seq, code):
        """输入4位告警代码生成告警消息并用base64加密,并通过告警代码匹配模块"""
        unitId = 10
        if code[0] == '0':
            unitId = 10  # DTM
        elif code[0] == '1':
            unitId = 20  # APK
        elif code[0] == '2':
            unitId = 40  # CCU
        elif code[0] == '3':
            unitId = 30  # ECU
        elif code[0] == '4':
            unitId = 60  # ACU
        else:
            print("未知模块")
        number = [3, 0, 0, 0]  # 記錄序號,第1到4位
        alert_level = [0]  # 故障級別,第13位
        alert_code = []  # 故障代碼,第14到15位
        alert_code.append(sixteen_to_ten(code[2:4]))
        alert_code.append(sixteen_to_ten(code[0:2]))
        data_type = [255]  # 發生或恢復,255是發生告警,0表示告警恢復,第16位
        data_len = [0, 0]  # 参数长度,第17到18位
        initial_data = number + generate_time() + alert_level + alert_code + data_type + data_len
        base64_data = base64_encode(initial_data)
        rcType = 4
        message = self.action_pushrecord(rcType, seq, unitId, base64_data)
        return message

    def report_charging_service_status(self, seq, status, gunNo):
        rcType = 3
        # 0-AVAILABLE,1-PREPARING,2-CHARGING,3-SUSPEND_EV,4-SUSPEND_EVSE,5-FINISHING,6-RESERVED,7-UNAVAILABLE,8-FAULTED
        params = f'{{"connectorId":{gunNo},"connectorState":"{status}","prevStateType":0,"subState":1,"runStatus":3,"cpStatus":3,"pileState":0}}'
        event_id = [1, 12, 2, 1]
        report_time = generate_time()
        event_code = [1, 0]
        param_type = [2]
        params_list = base64_decode_list(base64_encode(params))
        params_list_len = [len(params), 0]
        data = base64_encode(event_id + report_time + event_code + param_type + params_list_len + params_list)
        message = self.action_pushrecord(rcType, seq, 10, data)
        print(message)
        return message

    def report_ccu_event(self, seq, param, unitId):
        rcType = 3
        params = f'rebootType={param}'
        event_id = [1, 12, 2, 1]
        report_time = generate_time()
        event_code = [1, 32]
        param_type = [1]
        params_list = base64_decode_list(base64_encode(params))
        params_list_len = [len(params), 0]
        data = base64_encode(event_id + report_time + event_code + param_type + params_list_len + params_list)
        message = self.action_pushrecord(rcType, seq, unitId, data)
        return message

    def report_acu_event(self, seq, param, unitId):
        rcType = 3
        params = f'rebootType={param}'
        event_id = [2, 1, 2, 1]
        report_time = generate_time()
        event_code = [1, 64]
        param_type = [1]
        params_list = base64_decode_list(base64_encode(params))
        params_list_len = [len(params), 0]
        data = base64_encode(event_id + report_time + event_code + param_type + params_list_len + params_list)
        message = self.action_pushrecord(rcType, seq, unitId, data)
        return message

    def report_ecu_event(self, seq, param):
        rcType = 3
        params = f'rebootType={param}'
        event_id = [2, 1, 2, 1]
        report_time = generate_time()
        event_code = [1, 48]
        param_type = [1]
        params_list = base64_decode_list(base64_encode(params))
        params_list_len = [len(params), 0]
        data = base64_encode(event_id + report_time + event_code + param_type + params_list_len + params_list)
        message = self.action_pushrecord(rcType, seq, 30, data)
        return message

    def report_order(self,gunNo,):
        data = {
            "connectorId": gunNo,
            "LocalId": "DE1240B1GN1C00038J:1:639b7928:0",
            "version": 2,
            "RemoteId": "",
            "startIdTag": "POS3334353637011006",
            "stopIdTag": "POS3334353637011006",
            "startMode": 7,
            "chargeMode": 0,
            "chargeParam": 0,
            "chargeTimeSec": 1,
            "stopTime": 0,
            "meterStart": 2362400,
            "meterEnd": 2362400,
            "energy": 0,
            "stopReason": 1034,
            "accountFlag": 0,
            "svcEndFlag": 1,
            "startTranFlag": 0,
            "stopTranFlag": 0,
            "svcStartTime": "2022-12-16T03:44:40",
            "svcEndTime": "2022-12-16T03:44:41",
            "billing": {
                "startSoc": 58,
                "endSoc": 58,
                "startBalance": 10000,
                "endBalance": 10000,
                "carVin": "",
                "billTemplate": "AutelLocalTemplate",
                "totalMoney": 0,
                "svcMoney": 0,
                "chargeMoney": 0,
                "sectNum": 1,
                "sect": [{
                    "sectIndex": 0,
                    "sectAttribute": 127,
                    "sectEnergy": 0,
                    "svcMoneyUnit": 0,
                    "chargeMoneyUnit": 20000,
                    "sectStartTime": "03:44:40",
                    "sectStopTime": "03:44:41"
                }]
            }
        }


if __name__ == '__main__':
    print(CcuMessage().ccu_work_status(3, 3, 13))
    # a = ResponseMessage().set_local_start(32, 32, 3)
    # print(a)

ac_message.py
ac_message.py
from gevent import monkey;monkey.patch_all()
import gevent
import time

import websocket

from ac_message import *


class AcPile(CcuMessage, AcuMessage, DtmMessage, ResponseMessage, ReportEvent, EcuMessage):

    def __init__(self):
        # self.sn, self.csn, self.password, self.mac, self.pin = sn, csn, password, mac, pin
        super().__init__()
        self.connect_flag = True  # 桩和服务器的连接状态,默认为连接状态的
        self.seq = 1  # seq为整数的字符串表示格式,递增,到最⼤值后归零,取值范围为uint64
        self.framId = 2  # 帧序号,每次发送递增
        self.gun1_status = 0
        self.gun2_status = 0
        self.resv_message = "接收消息"
        self.resp_message = "回复消息"
        self.au = calc_pile_Authorization(self.csn, self.password, self.mac)
        self.url, self.ocpp_header, self.acmp_header = get_connect_info(self.ene, (self.sn, self.au))
        self.acmp_ws = websocket.WebSocket()
        try:
            self.acmp_ws.connect(self.url, header=self.acmp_header)
            self.escalationReporting(0, "connect", request_type="ACMP")  # 发送消息后注入报告
        except Exception as e:
            self.escalationReporting(0, "connect", request_type="ACMP", exception=e)  # 发送消息后注入报告
            print(e, "acmp连接失败")
        if self.sn[0] == 'D':
            gevent.spawn(self.ac_dc_real_time_data)
            gevent.spawn(self.ac_while_recv)
        else:
            gevent.spawn(self.ac_real_time_data)
            gevent.spawn(self.ac_while_recv)

    def ac_recv_msg(self):
        recv_message = self.acmp_ws.recv()
        self.escalationReporting(0, "send", request_type="ACMP")  # 发送消息后注入报告
        # print(f"{self.sn}:RECV:{recv_message}")
        return recv_message

    def ac_while_recv(self):
        """循环接受消息线程,一直收,死循环"""
        while 1:
            if self.connect_flag:  # 当连接状态为True的时候,可以一直收消息,进行后面的逻辑判断
                try:
                    recv_message = self.ac_recv_msg()  # 正常接收到的消息
                    if '"optType":1304' in recv_message:
                        self.resv_message = "接收平台消息:设置静音模式"
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        msgId = recv_data["payload"]["msgId"]
                        message = self.set_silent_mode(seq, 22, msgId)
                        # self.resp_message = message
                        self.ac_send_message(message)

                    elif '"optType":386' in recv_message:
                        recv_data = json.loads(recv_message)["payload"]["msgData"][0]["data"]
                        recv_data = base64_decode_json(recv_data)
                        if recv_data["code"] == 23:
                            self.resv_message = "接收平台消息:设置本地充电"
                            set_local_start = json.loads(recv_message)
                            seq = set_local_start["seq"]
                            msgId = set_local_start["payload"]["msgId"]
                            message = self.set_local_start(seq, 22, msgId)
                            self.ac_send_message(message)
                        elif recv_data["code"] == 25:
                            self.resv_message = "接收平台消息:查询本地充电"
                            sel_local_start = json.loads(recv_message)
                            seq = sel_local_start["seq"]
                            msgId = sel_local_start["payload"]["msgId"]
                            message = self.sel_local_start(seq, 22, msgId)
                            self.ac_send_message(message)
                        elif recv_data["code"] == 29:
                            set_time_zone = json.loads(recv_message)
                            seq = set_time_zone["seq"]
                            msgId = set_time_zone["payload"]["msgId"]
                            message = self.set_time_zone(seq, 22, msgId)
                            self.ac_send_message(message)
                        else:
                            pass
                    elif '"optType":369' in recv_message:
                        self.resv_message = "接收平台消息:设置日志级别"
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        msgId = recv_data["payload"]["msgId"]
                        srcAddr = recv_data["payload"]["msgData"][0]["msgHeader"]["dstAddr"]
                        message = self.set_log_level(seq, 32, msgId, srcAddr)
                        self.ac_send_message(message)

                    elif '"optType":372' in recv_message:
                        self.resv_message = "接收平台消息:查询日志级别"
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        msgId = recv_data["payload"]["msgId"]
                        srcAddr = recv_data["payload"]["msgData"][0]["msgHeader"]["dstAddr"]
                        message = self.sel_log_level(seq, 32, msgId, srcAddr)
                        self.ac_send_message(message)

                    elif '"optType":304' in recv_message:
                        self.resv_message = "接收平台消息:查询数据上报频率"
                        query_frequency_message = json.loads(recv_message)
                        data = query_frequency_message["payload"]["msgData"][0]["data"]
                        businessType = generate_businessType(data)
                        seq = query_frequency_message["seq"]
                        msgId = query_frequency_message["payload"]["msgId"]
                        # optType = query_frequency_message["payload"]["msgData"][0]["msgHeader"]["optType"] + 1
                        srcAddr = query_frequency_message["payload"]["msgData"][0]["msgHeader"]["dstAddr"]
                        message = self.sel_upload_frequency(seq, 33, msgId, srcAddr, businessType)
                        self.ac_send_message(message)

                    elif '"optType":306' in recv_message:
                        self.resv_message = "接收平台消息:设置数据上报频率"
                        set_frequency_message = json.loads(recv_message)
                        data = set_frequency_message["payload"]["msgData"][0]["data"]
                        businessType = generate_businessType(data)
                        seq = set_frequency_message["seq"]
                        msgId = set_frequency_message["payload"]["msgId"]
                        # optType = set_frequency_message["payload"]["msgData"][0]["msgHeader"]["optType"] + 1
                        srcAddr = set_frequency_message["payload"]["msgData"][0]["msgHeader"]["dstAddr"]
                        message = self.set_upload_frequency(seq, 33, msgId, srcAddr, businessType)
                        self.ac_send_message(message)

                    elif '"action":"UpdateFirmware"' in recv_message:
                        seq = json.loads(recv_message)["seq"]
                        status = "Accept"
                        message = self.update_firmware(seq, status)
                        self.ac_send_message(message)

                    elif '"optType":1570' in recv_message:
                        """禁用枪"""
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        data = recv_data["payload"]["msgData"][0]["data"]
                        gunNo = base64_decode_list(data)[0]
                        msgId = recv_data["payload"]["msgId"]
                        message = self.disable_pile(seq, 22, msgId, gunNo)
                        self.ac_send_message(message)
                        if gunNo == 1:
                            self.gun1_status = 7
                        if gunNo == 2:
                            self.gun2_status = 7
                        self.ac_send_message(self.report_charging_service_status(self.seq, 'UNAVAILABLE', gunNo))

                    elif '"optType":1572' in recv_message:
                        """解禁枪"""
                        recv_data = json.loads(recv_message)
                        seq = recv_data["seq"]
                        data = recv_data["payload"]["msgData"][0]["data"]
                        gunNo = base64_decode_list(data)[0]
                        msgId = recv_data["payload"]["msgId"]
                        message = self.disable_pile(seq, 22, msgId, gunNo)
                        self.ac_send_message(message)
                        if gunNo == 1:
                            self.gun1_status = 0

                        if gunNo == 2:
                            self.gun2_status = 0
                        self.ac_send_message(self.report_charging_service_status(self.seq, 'AVAILABLE', gunNo))

                    else:
                        pass

                except Exception as e:
                    recv_message = ""  # 如果异常里的话定义recv_message为空,并且把self.connect_flag置为False
                    self.connect_flag = False
                    print(f"当前接收消息协程报错,报错信息为:{e}")
            elif not self.connect_flag:  # 如果连接状态为False,表示断连了,啥也不干,等待心跳线程重连,这里继续等待就行
                time.sleep(10)

    def ac_send_message(self, data, message_type=None):
        """发送消息"""
        message = json.dumps(data)
        try:
            self.acmp_ws.send(message)
            self.escalationReporting(0, "recv", request_type="ACMP")  # 发送消息后注入报告
            if message_type == "CCU":
                self.escalationReporting(0, "CCU", request_type="ACMP")  # 发送消息后注入报告
            elif message_type == "ECU":
                self.escalationReporting(0, "ECU", request_type="ACMP")  # 发送消息后注入报告
            elif message_type == "DTM":
                self.escalationReporting(0, "DTM", request_type="ACMP")  # 发送消息后注入报告
            # print(f"{self.sn}:SEND:{message}")
            self.seq += 1
            self.framId += 1
        except Exception as e:
            print(e)

    def ac_send_pushrecord(self):
        rcType = int(input("请输入消息类型:"))
        if rcType == 3:
            # message = self.report_acu_event(self.seq)
            message = self.report_charging_service_status(self.seq)
            self.ac_send_message(message)
        elif rcType == 4:
            alert_code = input("请输入告警代码")
            message = self.report_alert(self.seq, alert_code)
            self.ac_send_message(message)

    def ac_send_acu1_realtata(self):
        message = self.acu_work_status(self.seq, 60, self.framId)
        self.ac_send_message(message)

    def ac_send_acu2_realtata(self):
        message = self.acu_work_status(self.seq, 61, self.framId)
        self.ac_send_message(message)

    def ac_send_ccu_realdata(self):
        self.ac_send_message(self.ccu_work_status(self.seq, 40, self.framId), "CCU")
        self.ac_send_message(self.ccu_work_status(self.seq, 41, self.framId), "CCU")
        self.ac_send_message(self.ccu_telemetry_data(self.seq, 40, self.framId), "CCU")
        self.ac_send_message(self.ccu_telemetry_data(self.seq, 41, self.framId), "CCU")
        self.ac_send_message(self.ccu_telesignalling_data(self.seq, 40, self.framId), "CCU")
        self.ac_send_message(self.ccu_telesignalling_data(self.seq, 41, self.framId), "CCU")
        self.ac_send_message(self.ccu_telemetry_charging_process(self.seq, 40, self.framId), "CCU")
        self.ac_send_message(self.ccu_telemetry_charging_process(self.seq, 41, self.framId), "CCU")

    def ac_send_ecu_realdata(self):
        self.ac_send_message(self.ecu_work_status(self.seq, self.framId), "ECU")
        self.ac_send_message(self.ecu_telemetry_data(self.seq, self.framId), "ECU")
        self.ac_send_message(self.ecu_telesignalling_data(self.seq, self.framId), "ECU")
        self.ac_send_message(self.ecu_charging_data(self.seq, self.framId), "ECU")

    def ac_send_dtm_realdata(self):
        self.ac_send_message(self.dtm_work_status(self.seq, self.framId))
        self.ac_send_message(self.pile_realtime_status(self.seq, self.framId, 1, self.gun1_status), "DTM")
        self.ac_send_message(self.pile_realtime_status(self.seq, self.framId, 2, self.gun2_status), "DTM")

    def ac_dc_real_time_data(self):
        """充电桩通过acmp需要发送的实时数据"""
        while 1:
            if self.connect_flag:  # 当连接状态为True的时候,可以一直发ccu状态数据
                try:
                    self.ac_send_dtm_realdata()
                    self.ac_send_ecu_realdata()
                    self.ac_send_ccu_realdata()
                    time.sleep(15)
                except Exception as e:  # 当发送消息失败的时候
                    print(f"ccu状态数据发送出错了,错误信息为:{e}")
                    self.connect_flag = False  # 发送消息发生错误了就把连接状态置为False
            elif not self.connect_flag:  # 连接状态为False了那么需要重新连接服务器
                try:
                    self.acmp_ws.connect(self.url, header=self.acmp_header)
                    self.connect_flag = True  # 如果重连成功了,那么连接状态置为True
                except Exception as e:
                    print(f"重连失败了,连接失败的信息为:{e}")
                    time.sleep(10)  # 10s后继续重连服务器

    def ac_real_time_data(self):
        """充电桩通过acmp需要发送的实时数据"""
        while 1:
            if self.connect_flag:  # 当连接状态为True的时候,可以一直发ccu状态数据
                try:
                    self.ac_send_acu1_realtata()
                    self.ac_send_acu2_realtata()
                    self.ac_send_message(self.dtm_work_status(self.seq, self.framId))
                    time.sleep(10)
                except Exception as e:  # 当发送消息失败的时候
                    print(f"acu状态数据发送出错了,错误信息为:{e}")
                    self.connect_flag = False  # 发送消息发生错误了就把连接状态置为False
            elif not self.connect_flag:  # 连接状态为False了那么需要重新连接服务器
                try:
                    self.acmp_ws.connect(self.url, header=self.acmp_header)
                    self.connect_flag = True  # 如果重连成功了,那么连接状态置为True
                except Exception as e:
                    print(f"重连失败了,连接失败的信息为:{e}")
                    time.sleep(10)  # 10s后继续重连服务器


if __name__ == '__main__':
    AcPile("china_test", "DE1240B1GN1C00029J")
    # SubscribePush("china_test", "DE1240B1GN1C00029J", "xiejieming@autel.com", "a12345678")

ac_pile.py
ac_pile.py
from gevent import monkey;monkey.patch_all()

from datetime import datetime, timedelta
import hashlib
import base64

from conf import BASIC_SALT, EneGatewayHost, Message


def getUTC0():
    """返回:2022-07-06T00:36:39.000Z"""
    from datetime import datetime, timedelta
    now_time = datetime.now()
    utc_time = now_time - timedelta(hours=8)  # UTC只是比北京时间提前了8个小时
    utc_time = utc_time.strftime("%Y-%m-%dT%H:%M:%S" + ".000Z")
    return utc_time


def get_utc0_time():
    """获取UTC0时区时间,返回:20220706003708"""
    utc_time = datetime.now() - timedelta(hours=8)
    utc_time = utc_time.strftime("%Y%m%d%H%M%S")
    return utc_time


def calc_pile_Authorization(ctrlSn, pwd, mac):
    sha256 = hashlib.sha256()
    sha256.update(f"{ctrlSn}:{pwd}:{BASIC_SALT}:{mac.replace('-', ':').lower()}".encode('utf-8'))
    in_data = sha256.digest()
    res = 'Authorization:Basic ' + str(base64.b64encode((ctrlSn + ":" + base64.b64encode(in_data).decode("utf-8")).
                                                        encode('utf-8')).decode('utf-8'))
    return res


def get_connect_info(ene, pile_info):
    """
    获取连接信息,返回连接的url和header
    :param ene:         环境(china_dev,china_test,china_prod,England_test,USA_test,USA_prod,England_prod,Europe_prod)
    :param pile_info:   桩信息
    :return:
    """
    url = f"ws://{getattr(EneGatewayHost, ene)}/ws/webSocket?sn={pile_info[0]}"
    header = [pile_info[1], "Sec-WebSocket-Protocol:ocpp1.6", "Upgrade:websocket"]
    return url, header


def generate_msg(msg_type, requests_type, message_flag, pile_connectorId=1, idTag=None,
                 transactionId=None, change_voltage=220, change_electric=32, change_value=None, stop_charging_id=None,
                 Power_Phase=1, gunNO='1', lastTime=None, pileSN=None, seq=None, eventType=1, securityHappen=1,
                 card_id=None, soc_value=None):
    out_data = []
    msg = getattr(Message, msg_type)
    for data in msg:
        out_data.append(data)
    if msg_type != "Accepted":
        out_data.insert(0, message_flag)
        out_data.insert(0, requests_type)
    if msg_type == "Accepted":
        out_data.insert(0, requests_type)
        if idTag is not None:
            out_data.insert(1, idTag)
        if stop_charging_id is not None:
            out_data.insert(1, stop_charging_id)

    if msg_type in ["Available", "Preparing", "Faulted", "Reserved", "StartTransaction", "MeterValues",
                    "SuspendedEVSE", "SuspendedEV", "Finishing", "Unavailable", "Charging"]:
        out_data[-1]["connectorId"] = pile_connectorId

    if msg_type == "Authorize":
        out_data.append({"idTag": idTag})
    elif msg_type == "StartTransaction":  # 开始充电
        out_data[-1]["idTag"] = idTag
        # out_data[-1]["idTag"] = ""
        out_data[-1]["timestamp"] = getUTC0()
    elif msg_type == "MeterValues":
        out_data[-1]["transactionId"] = transactionId
        out_data[-1]["meterValue"] = [{'sampledValue': [], "timestamp": getUTC0()}]  # 中间
        # out_data[-1]["meterValue"].append({"timestamp": getUTC0()})
        out_data[-1]["meterValue"][0]['sampledValue'].append(
            {'measurand': 'Energy.Active.Import.Register', 'unit': 'Wh', 'value': change_value})
        for i in range(1, 4):
            out_data[-1]["meterValue"][0]['sampledValue'].append(
                {'measurand': 'Voltage', 'phase': f'L{i}', 'unit': 'V', 'value': f"{change_voltage}.000001"})
        for j in range(1, 4):
            out_data[-1]["meterValue"][0]['sampledValue'].append({"value": f"{change_electric}.000001", "unit": "A",
                                                                  "measurand": "Current.Export", "phase": f"L{j}"})
        try:
            out_data[-1]["meterValue"][0]['sampledValue'].append(
                {"value": f"{int(change_electric) * int(change_voltage) * int(Power_Phase)}.1", "unit": "W",
                 "measurand": "Power.Offered"})
        except Exception as e:
            print(e, change_electric, change_voltage, Power_Phase)
            out_data[-1]["meterValue"][0]['sampledValue'].append(
                {"value": f"{int(220 * 32 * 3)}.1", "unit": "W",
                 "measurand": "Power.Offered"})
        if soc_value is not None:
            out_data[-1]["meterValue"][0]['sampledValue'].append(
                {"context": "Sample.Periodic", "measurand": "SoC", "value": str(soc_value)})
    elif msg_type == "StopTransaction":
        out_data[-1]["idTag"] = idTag
        out_data[-1]["transactionId"] = transactionId
        out_data[-1]["timestamp"] = getUTC0()
        out_data[-1]["reason"] = "Remote"
        out_data[-1]["meterStop"] = change_value
    elif msg_type == "SUSPENDEDEV_TIMEOUT":
        out_data[3]['autel']["data"]["gunNO"] = gunNO
        out_data[3]['autel']["data"]['lastTime'] = lastTime
        out_data[3]['autel']["data"]['pileSN'] = pileSN
        out_data[3]['autel']["data"]['transactionId'] = transactionId
        out_data[3]['autel']['seq'] = seq
    elif msg_type == "SECURITY_EVENT":
        out_data[3]['autel']['seq'] = seq
        out_data[3]['autel']['data'][0]['eventType'] = eventType
        out_data[3]['autel']['data'][0]['securityHappen'] = securityHappen
        out_data[3]['autel']['data'][0]['timestamp'] = getUTC0()
    elif msg_type == "POS_RULE":
        out_data[3]['autel']['data']['pileNum'] = pileSN
    elif msg_type == "card_Authorize":
        out_data.append({"idTag": card_id})
    elif msg_type == "card_MeterValues":
        out_data[-1]["connectorId"] = pile_connectorId
        out_data[-1]["idTag"] = card_id
        out_data[-1]["timestamp"] = getUTC0()
    elif msg_type == "Config":
        out_data[-1]['autel']['data']['ChargePoint_SN'] = pileSN
    elif msg_type == "TARIFF_RULE_1":
        out_data[-1]['autel']['ChargePoint_SN'] = pileSN
    elif msg_type == "TARIFF_RULE_2":
        out_data[-1]['autel']['data']['pileNum'] = pileSN
    elif msg_type == "MeterValues_sort":
        out_data[-1]["connectorId"] = pile_connectorId
        out_data[-1]["transactionId"] = transactionId
        out_data[-1]['meterValue'][0]['timestamp'] = getUTC0()
    return out_data


def generateAcPile(in_range):
    for index in range(in_range[0], in_range[1]):
        pile_sn = f'AE0022TEST9F{index:0>5}D'
        ctrlSn = f"C00022F999{index:0>5}"
        in_data = f"{index:0>6}"
        mac = f"11:aa:33:{in_data[0:2]}:{in_data[2:4]}:{in_data[4:6]}"
        payload = {
            "bluetoothMac": "",
            "ctrlSn": ctrlSn,
            "lacMac": "",
            "mac": mac,
            "password": "123456",
            "pileSn": pile_sn,
            "pin": "01234567",
            "productModel": "",
            "sealerNo": ""}
        yield payload, index


def generateDCPile(in_range):
    for index in range(in_range[0], in_range[1]):
        pile_sn = f'DE1240B1TEST{index:0>5}J'
        ctrlSn = f"C06G12TEST{index:0>5}"
        in_data = f"{index:0>6}"
        mac = f"0a:f7:2d:{in_data[0:2]}:{in_data[2:4]}:{in_data[4:6]}"
        payload = {
            "bluetoothMac": "",
            "ctrlSn": ctrlSn,
            "lacMac": "",
            "mac": mac,
            "password": "123456",
            "pileSn": pile_sn,
            "pin": "01234567",
            "productModel": "",
            "sealerNo": ""
        }
        yield payload, index


if __name__ == '__main__':
    import pprint
    pprint.pprint(generate_msg("StartTransaction", 2, "99999999999", idTag="01AUTEL0000000000003"))

common.py
common.py
from gevent import monkey;monkey.patch_all()


BASIC_SALT = ""

firmwareVersion = {
}

logType = [
]


class EneGatewayHost:
    china_dev = ""  # 中国开发
    china_test = ""  # 中国测试
    china_prod = ""  # 中国生产
    England_test = ""  # 英国测试
    USA_test = ""  # 美西测试
    USA_prod = ""  # 美国生产
    England_prod = ""  # 英国生产
    Europe_prod = ""  # 欧洲生产
    Canada_prod = ""  # 加拿大生产
    ene_wb_dev_cn = ""  # 联调环境
    oci_test = ""


class PlatFormHost:
    china_test = ""  # 中国测试
    England_test = ""  # 英国测试
    oci_test = ""
    china_dev = "
    USA_test = ""
    USA_prod = ""


class Message:
    BootNotification = ["BootNotification", {"chargePointModel": "MaxiChargerAC", "chargePointVendor": "Autel",
                                             "firmwareVersion": firmwareVersion[
                                                 "1.05.57/1.06.50"]}]  # 引导通知,充电点类型,充电点供应商,固件版本
    Heartbeat = ["Heartbeat", {}]  # 心跳消息
    Available = ["StatusNotification",
                 {"status": "Available", "errorCode": "NoError", "info": "eCp_12V"}]  # 可用状态
    Preparing = ["StatusNotification",
                 {"status": "Preparing", "errorCode": "NoError", "info": "eCp_6V"}]  # 插枪准备状态
    Faulted = ["StatusNotification",
               {"vendorErrorCode": "6,", "status": "Faulted", "errorCode": "GroundFailure",
                "info": "eCp_12V"}]  # 故障状态
    Reserved = ["StatusNotification",
                {"status": "Reserved", "errorCode": "NoError", "info": "eCp_12V"}]  # 预约状态
    SuspendedEVSE = ["StatusNotification",
                     {"status": "SuspendedEVSE", "errorCode": "NoError", "info": "eCp_6V"}]  # 暂停状态
    SuspendedEV = ["StatusNotification",  # SuspendedEV暂停
                   {"status": "SuspendedEV", "errorCode": "NoError", "info": "eCp_6V"}]
    Finishing = ["StatusNotification",
                 {"status": "Finishing", "errorCode": "NoError", "info": "eCp_6V"}]  # Finishing充电完成
    Unavailable = ["StatusNotification",
                   {"status": "Unavailable", "errorCode": "NoError", "info": "eCp_12V"}]  # 不可用
    Charging = ["StatusNotification",
                {"status": "Charging", "errorCode": "NoError", "info": "eCp_6V"}]
    status = [{"status": "Accepted"}]
    # Authorize = ["Authorize", {"idTag": "1531974243087556609"}]
    SUSPENDEDEV_TIMEOUT = ['DataTransfer', {'autel': {'cmd': 'SUSPENDEDEV_TIMEOUT', 'data': {}, },
                                            'data': '', 'vendorId': 'Autel'}]  # EV暂停消息
    SECURITY_EVENT = ["DataTransfer", {"vendorId": "Autel",
                                       "data": "", "autel": {"cmd": "SECURITY_EVENT", "data": [{}]}}]

    # 充电逻辑的数据
    Accepted = [{"status": "Accepted"}]  # 桩发送收到充电了
    Authorize = ["Authorize"]  # 充电消息回复
    card_Authorize = ["Authorize"]
    meterStart = [{"meterStart": 0}]
    MeterValues = ["MeterValues", {}]
    MeterValues_sort = ["MeterValues", {"meterValue": [
        {"sampledValue": [{"value": "0",
                           "context": "Transaction.Begin",
                           "format": "Raw",
                           "measurand": "Energy.Active.Import.Register",
                           "phase": "L3",
                           "location": "Outlet",
                           "unit": "Wh"}]}]}]
    StartTransaction = ["StartTransaction", {"meterStart": 0}]  # 开启充电,加idTag和timestamp
    card_MeterValues = ["StartTransaction", {"meterStart": 0}]
    Rejected = [{"status": "Rejected"}]  # 结束充电
    StopTransaction = ["StopTransaction", {}]

    POS_RULE = ["DataTransfer", {"vendorId": "Autel", "data": "", "autel": {"cmd": "POS_RULE", "seq": "10",
                                                                            "data": {"transactionId": 2000056984}}}]
    Config = ["DataTransfer",
              {"vendorId": "Autel", "data": "",
               "autel": {"cmd": "CHARGE_CTRL", "seq": "3449862a-3e54-46f1-b568-e407dbec389a",
                         "data": {"sn": "",
                                  "cpConfig": "V1.05.57,V1.06.50,V00.00.00,V00.00.00,V00.00.00,160,0,,0,0",
                                  "cpSchedule": "0,0,0,0,0,0,0,0",
                                  "pileInfo": {
                                      "version": [{"board": "0", "type": "__UNI__OTA_ECC0101", "ver": "V1.05.57"},
                                                  {"board": "1", "type": "__UNI__OTA_ECP0201", "ver": "V1.06.50"}],
                                      "switchCurrent": "320", "maxCurrentSet": "160", "maxCur": "320", "maxSoc": "0",
                                      "ssid": "TestPile", "startMode": "0", "chargeMode": "0",
                                      "libVer": "10", "sub-G": "0", "subGOn": "0", "rfidOn": "1", "netType": ["WIFI"]},
                                  "support": {"autoResver": "0",
                                              "finishing2pre": "1", "appClearConfig": "1", "restoreSetting": "1"},
                                  "transactionId": 2000034295}}}]
    TARIFF_RULE_1 = ["DataTransfer", {"vendorId": "Autel", "data": "", "autel": {"cmd": "TARIFF_RULE",
                                                                                 "ChargePoint_SN": "",
                                                                                 "data": {"Result": "BusyNow",
                                                                                          "transactionId": 2000034295}}}]
    TARIFF_RULE_2 = ["DataTransfer", {"vendorId": "Autel", "data": "", "autel": {"cmd": "TARIFF_RULE", "seq": "0",
                                                                                 "data": {
                                                                                     "pileNum": "",
                                                                                     "transactionId": 2000034295}}}]

conf.py
conf.py
ene = "china_test"  # "china_test" , "England_test", "USA_test"
scene = "test"  # 场景:static:桩只上线发心跳   timedCharging:家桩定时充电   SUSPENDEDEV_TIMEOUT:暂停充电   test:调试模式
pile_type = "AC"  # AC交流      DC直流

outTimeLevel = [1000, 5000, 30000]  # 超时时间

# ge = [1, 10000]
# ge1 = [1, 1001]
# ge2 = [3000, 4000]
# ge3 = [74001, 76001]
# ge4 = [76001, 78001]
# ge5 = [78001, 79001]
# ge6 = [79001, 80001]

ge = [60001, 70001]
ge1 = [62001, 62501]
ge2 = [62501, 63001]
ge3 = [1002, 1503]
ge4 = [1503, 2004]
# ge5 = [78001, 79001]
# ge6 = [79001, 80001]

eneGeneratePileAndUser.py
eneGeneratePileAndUser.py
from gevent import monkey;monkey.patch_all()
import logging


def get_logger(log_dir):
    logger = logging.getLogger()
    fh = logging.FileHandler(log_dir, encoding='utf-8')
    ch = logging.StreamHandler()
    logging.root.setLevel(logging.NOTSET)
    fh.setLevel(logging.DEBUG)
    ch.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    logger.addHandler(fh)
    logger.addHandler(ch)
    return logger


logger_boj = get_logger(f"./locustPile.log")

logger.py
logger.py
from gevent import monkey;monkey.patch_all()

from oc_pile import OcPile
from ac_pile import AcPile


class MainPile(AcPile, OcPile):
    def __init__(self, ene, in_pile, locustUser):
        self.ene = ene
        self.reportObj = locustUser.user.environment.events.request.fire  # 注入报告的
        self.sn = in_pile['pileSn']
        self.csn = in_pile['ctrlSn']
        self.password = in_pile['password']
        self.mac = in_pile['mac']
        self.pin = in_pile['pin']
        OcPile.__init__(self)  # 调用 ocpp协议
        if self.sn[0] == "D":
            AcPile.__init__(self)   # 调用 Acmp协议

    def escalationReporting(self, total_time, name, response_length=0,
                            request_type="OCPP", exception=None):
        """日志上报"""
        self.reportObj(request_type=request_type,
                       name=name,
                       response_time=total_time,
                       response_length=response_length,
                       response=None,
                       context=None,
                       exception=exception)

main_pile.py
main_pile.py
from gevent import monkey;

monkey.patch_all()
import time
import os
import sys
import random

from locust import User, TaskSet, task
from main_pile import MainPile
from eneGeneratePileAndUser import ene, scene, pile_type
from common import generateAcPile, generateDCPile
from eneGeneratePileAndUser import ge as ge


class MyTaskSet(TaskSet):

    def on_start(self):
        print("初始化用户")
        self.pile_data, self.index = self.user.ge_pile_data.__next__()
        print(self.pile_data)
        self.pileObj = MainPile(self.user.host, self.pile_data, self)

    @task
    def pile_change_status(self):
        if scene == "static":
            while 1:
                time.sleep(1000)
        elif scene == "timedCharging":
            type_lis = ["Preparing", "Available"]
            if self.index % 2 == 1:  # 如果桩是偶数的,就启动充电
                while 1:
                    self.pileObj.oc_pile_start_change()  # 开启主动充电
                    time.sleep(random.randint(180, 720))  # 充电时长
                    self.pileObj.oc_pile_stop_change()  # 停止充电
                    time.sleep(random.randint(12, 18))  # 休息2.3分钟后继续充电
            elif self.index % 2 == 0:  # 如果桩是奇数的,就正常启动充电
                while 1:
                    self.pileObj.oc_send_message(random.choice(type_lis), 2)  # 切换状态
                    time.sleep(random.randint(120, 180))
        elif scene == "SUSPENDEDEV_TIMEOUT":  # 暂停充电的场景
            type_lis = ["Preparing", "Available"]
            if self.index % 2 == 1:  # 如果是标志需要充电的桩,就去循环充电,一直循环
                while 1:
                    time.sleep(random.randint(5, 30))  # 随机延迟5-30s后开启充电
                    self.pileObj.oc_pile_start_change()  # 开启主动充电
                    time.sleep(random.randint(1800, 3600))  # 充电30-60分钟
                    if self.index % 4 == 1:  # 如果能整除4,那么这部分开启暂停充电
                        self.pileObj.Pause_charging_flag = True  # 开启暂停充电
                        self.pileObj.oc_send_message("SuspendedEV", self.pileObj.oc_requests_type2, pile_connectorId=1)
                        time.sleep(random.randint(3600, 4800))  # 暂停1小时或者1小时20分钟
                        self.pileObj.Pause_charging_flag = False  # 取消暂停
                        time.sleep(100)  # 再充电100s后桩停止充电
                    self.pileObj.oc_pile_stop_change()  # 停止充电
                    time.sleep(random.randint(120, 180))  # 休息2.3分钟后继续充电
            elif self.index % 2 == 0:  # 如果不是需要充电的就去2-3分钟切换一次状态
                while 1:
                    self.pileObj.oc_send_message(random.choice(type_lis), 2)  # 切换状态
                    time.sleep(random.randint(120, 180))
        elif scene == "test":  # 调试模式
            while 1:
                time.sleep(random.randint(1200, 1800))  # 充电时长
                out_num = random.randint(1, 10)
                if out_num == 5:  # 切换状态Fault
                    self.pileObj.oc_send_message("Faulted", self.pileObj.oc_requests_type2)  # 进入故障
                    time.sleep(random.randint(1200, 1800))
                    self.pileObj.oc_send_message("Available", self.pileObj.oc_requests_type2)  # 开始可用

    def on_stop(self):
        print("执行完后的步骤")


class WebSitUser(User):
    host = ene  # 指定主机地址   "china_test" , "England_test"
    if pile_type == "AC":
        ge_pile_data = generateAcPile(ge)
    elif pile_type == "DC":
        ge_pile_data = generateDCPile(ge)
    tasks = [MyTaskSet]
    min_wait = 100
    max_wait = 200


if __name__ == "__main__":
    current_dir = os.getcwd()
    sys.path.insert(0, current_dir)
    os.environ['NO_PROXY'] = 'stackoverflow.com'
    os.system("locust -f worker1.py")

master.py
master.py
from gevent import event, monkey;monkey.patch_all()
import gevent
import json
import time
import queue
import random

import websocket
import requests

from common import calc_pile_Authorization, get_connect_info, generate_msg
from conf import logType, PlatFormHost
from eneGeneratePileAndUser import outTimeLevel


class OcPile:

    def __init__(self):
        self.oc_connect_event = event.Event()  # 信号量,connect连接成功后才解开锁
        self.oc_autoChangeFlag = False  # 自动充电标志
        self.oc_connect_flag = False
        self.oc_outTime_flag = False  # 超时标志,默认False没有超时,发送消息3次超过20s就超时,桩需要断网重连
        self.oc_outTime_dic = {}  # 桩发送消息超时的的记录下来,重发   {"2022112207063100000004": 3} 3次,类似这样统计发送次数
        # 在 while_heartbeat协程中处理重发逻辑和判断是不是3条消息超过20s了
        self.oc_message_time_dic = {}  # 消息收到的时间记录
        self.oc_Pause_charging_flag = None  # 暂停充电标志,默认None正常充电
        self.oc_card_value = 0
        self.oc_start_time = time.time()
        self.oc_Power_Phase = 3  # 电源相数默认3相
        self.oc_card_Charging_flag = False  # 默认没有刷卡充电,刷卡充电的标签
        self.oc_time_flag = 1
        self.oc_requests_type2 = 2  # 请求数据类型
        self.oc_requests_type3 = 3  # 请求数据类型
        self.oc_idTag_queue = queue.Queue()  # 充电id
        self.oc_stop_charging_id_queue = queue.Queue()  # 停止充电的id标志
        self.oc_change_flag = False  # 充电状态标志,默认没有充电
        self.oc_transactionId_queue = queue.Queue()  # 交易id
        self.oc_change_electric = 10  # 充电电流        32
        self.oc_change_voltage = 110  # 充电电压        220
        self.oc_change_energy = int(self.oc_change_electric) * int(self.oc_change_voltage) * int(self.oc_Power_Phase)
        self.oc_pile_state = None  # 默认桩状态正常
        self.oc_au = calc_pile_Authorization(self.csn, self.password, self.mac)
        self.oc_url, self.oc_header = get_connect_info(self.ene, (self.sn, self.oc_au))
        self.oc_ws = websocket.WebSocket()
        self.oc_connect_server()  # 连接服务器,做重连逻辑等各种操作

    def oc_connect_server(self):
        """连接服务器,做重连逻辑"""
        while not self.oc_connect_flag:
            start_time = time.time()
            try:
                self.oc_ws.connect(self.oc_url, header=self.oc_header)
            except Exception as e:  # 捕获到错误说明连接失败了,间隔1s后继续连接
                total_time = int((time.time() - start_time) * 1000)
                self.escalationReporting(total_time, "Connect", exception=e)  # 连接失败了日志上报
                time.sleep(2)
            else:
                self.oc_connect_event.set()  # 如果没有捕获到错误说明连接成功了,那么set()解除其他任务的阻塞
                self.oc_connect_flag = True  # 连接成功那么循环结束
                total_time = int((time.time() - start_time) * 1000)
                self.escalationReporting(total_time, "Connect")  # 连接成功了日志上报
                gevent.spawn(self.oc_while_recv)  # 连接成功后开启收消息协程
                time.sleep(0.1)
                self.oc_send_message("BootNotification", self.oc_requests_type2)  # 发送boot消息
                time.sleep(0.1)
                self.oc_send_message("TARIFF_RULE_1", 2, pileSN=self.sn)
                time.sleep(1)
                self.oc_send_message("TARIFF_RULE_2", 2, pileSN=self.sn)
                self.oc_pile_charging_Preparing()  # 插枪准备充电状态
                gevent.spawn(self.oc_while_heartbeat)  # 心跳协程
                gevent.spawn(self.oc_while_frontLog)  # 日志上报协程
                gevent.spawn(self.oc_while_processingTimeout)  # 消息超时处理协程
                # 连接成功后开启了 3 条协程

    def oc_Reconnection(self):
        """重连逻辑"""
        start_time = time.time()
        try:
            self.oc_ws.connect(self.oc_url, header=self.oc_header)
        except Exception as e:  # 捕获到错误说明连接失败了,间隔1s后继续连接
            total_time = int((time.time() - start_time) * 1000)
            self.escalationReporting(total_time, "oc_Reconnection", exception=e)  # 连接失败了日志上报
            time.sleep(10)
        else:
            # 重连的逻辑不需要开启其他协程,以前都开启了,注入重连日志就行
            self.oc_connect_flag = True  # 连接成功连接状态置为True
            total_time = int((time.time() - start_time) * 1000)
            self.escalationReporting(total_time, "oc_Reconnection")  # 重连成功了日志上报
            time.sleep(0.1)
            self.oc_send_message("BootNotification", self.oc_requests_type2)
            # websocket鉴权ok后需要Boot认证,发送Boot需要30s内回复,30s内不回复桩就重连
            time.sleep(0.1)
            self.oc_send_message("TARIFF_RULE_1", 2, pileSN=self.sn)
            time.sleep(1)
            self.oc_send_message("TARIFF_RULE_2", 2, pileSN=self.sn)
            self.oc_pile_charging_Preparing()  # 插枪准备充电状态

    def oc_pile_charging_Preparing(self):
        """模拟桩插枪"""
        self.oc_send_message("Available", self.oc_requests_type2)
        self.pile_state = "Available"
        time.sleep(0.1)
        self.oc_send_message("Preparing", self.oc_requests_type2)
        self.pile_state = "Preparing"

    def oc_change_pile_state(self, state):
        """切换桩的状态"""
        self.oc_send_message(state, self.oc_requests_type2)
        self.pile_state = state

    def oc_frontLog_acceptEventLog(self, in_id="localPrintf"):
        """模拟桩接口上报日志"""
        in_data = random.choice(logType)
        url = f"{getattr(PlatFormHost, self.ene)}/api/log-service/frontLog/acceptEventLog?" \
              f"data={in_data}&id={in_id}&sn={self.sn}"
        result_time = 0
        reps = ""
        start_time = time.time()
        try:
            reps = requests.post(url).json()
            result_time = int((time.time() - start_time) * 1000)
            if reps['message'] == 'OK':
                self.escalationReporting(result_time, "frontLog", request_type="HTTP",
                                         response_length=len(json.dumps(reps).encode("utf-8")))
            else:
                self.escalationReporting(result_time, "frontLog", request_type="HTTP",
                                         response_length=len(json.dumps(reps).encode("utf-8")),
                                         exception=reps['message'])
        except Exception as e:
            self.escalationReporting(result_time, "frontLog", request_type="HTTP",
                                     response_length=len(json.dumps(reps).encode("utf-8")),
                                     exception=e)

    def oc_while_frontLog(self):
        while 1:
            time.sleep(5)
            self.oc_frontLog_acceptEventLog()
            time.sleep(10)

    def oc_pile_start_change(self):
        """桩主动开启充电,定时充电"""
        self.oc_send_message("StartTransaction", self.oc_requests_type2, idTag="01AUTEL0000000000003")
        self.autoChangeFlag = True
        gevent.spawn(self.oc_pile_changing)

    def oc_pile_changing(self):
        """桩定时充电的逻辑,只要autoChangeFlag的标志为True,就一直充电"""
        value = 0
        lastTime = 0
        seq = 0
        # 开启充电后需要得到一个 transactionId  拿到
        transactionId = self.oc_transactionId_queue.get()
        self.oc_send_message("Charging", self.oc_requests_type2, pile_connectorId=1)  # 发送桩状态充电中
        self.oc_send_message("MeterValues_sort", self.oc_requests_type2, transactionId=transactionId)  # 发送充电消息
        while 1:
            if self.oc_connect_flag:  # 如果桩处于连接过程中,发送充电的电流电压消息
                try:
                    if self.autoChangeFlag and self.Pause_charging_flag is None:
                        """当桩自动充电的标识为真  并且 桩的暂停充电的标志为None  并且  桩处于连接的时候  --正常发送电流电压"""
                        self.oc_send_message("MeterValues", self.oc_requests_type2, change_value=str(value),
                                             transactionId=transactionId,
                                             change_voltage=self.oc_change_voltage,
                                             change_electric=self.oc_change_electric,
                                             Power_Phase=self.oc_Power_Phase)
                        time.sleep(10)
                        value += int(round(self.oc_change_energy / 3600 * 10 * self.oc_Power_Phase, 1))

                    elif not self.autoChangeFlag:
                        """当自动充电的标识为假的时候表示充电要结束了"""
                        self.oc_send_message("StopTransaction", self.oc_requests_type2, idTag="01AUTEL0000000000003",
                                             transactionId=transactionId,
                                             change_value=str(value))
                        time.sleep(0.1)
                        self.oc_send_message("Finishing", self.oc_requests_type2)  # 结束本次充电, break中止循环,
                        time.sleep(1)
                        self.oc_send_message("Preparing", self.oc_requests_type2)
                        break

                    elif self.Pause_charging_flag:  # 如果暂停充电的flag是True,那么当前暂停充电,切换桩状态,并且发送暂停消息
                        for i in range(3):
                            time.sleep(3)
                            lastTime += 3
                            seq += 1
                            self.oc_send_message("SUSPENDEDEV_TIMEOUT", self.oc_requests_type2, gunNO='1',
                                                 lastTime=str(lastTime),
                                                 pileSN=self.sn, transactionId=transactionId, seq=seq)
                        time.sleep(0.1)
                        self.oc_send_message("MeterValues", self.oc_requests_type2, change_value=str(value),
                                             transactionId=transactionId,
                                             change_voltage=self.oc_change_voltage, change_electric=0,
                                             Power_Phase=self.oc_Power_Phase)
                        # 当前暂停状态为True,所有一直处于暂停状态,发送暂停相关的消息
                    elif self.Pause_charging_flag is False:
                        # 如果暂停充电的flag是False,表示取消了暂停,切换桩的状态为charing,把flag置为None
                        self.oc_send_message("Charging", self.oc_requests_type2, pile_connectorId=1)  # 枪1发送充电中的消息
                        self.Pause_charging_flag = None
                        lastTime = 0
                        seq = 0
                    elif not self.oc_connect_flag:
                        """如果充电的时候连接状态断开了,啥也不干,等10s,等心跳线程和recv线程重连就行了"""
                        time.sleep(10)
                except Exception as e:
                    """如果充电 过程中报错了,那么是发送消息异常了,判断为当桩断联了"""
                    self.oc_connect_flag = False
                    print("pile_changing", f"充电过程中桩断连了,错误信息为:{e},当前未结束的订单为:{transactionId}")
            else:  # 如果桩不处于连接中,那么就暂停发送消息等待self.connect_flag  连接状态为True
                time.sleep(10)

    def oc_pile_stop_change(self):
        """桩主动停止定时充电"""
        self.autoChangeFlag = False

    def oc_changer_ing(self):
        value = 0
        idTag = self.oc_idTag_queue.get()
        self.oc_send_message("Accepted", self.oc_requests_type3, idTag=idTag)
        time.sleep(0.1)
        self.oc_send_message("Authorize", self.oc_requests_type2, idTag=idTag)
        time.sleep(0.1)
        self.oc_send_message("Charging", self.oc_requests_type2, pile_connectorId=1)  # 枪1发送充电中的消息
        time.sleep(0.1)
        self.oc_send_message("StartTransaction", self.oc_requests_type2, idTag=idTag)
        # print("开启充电结束了,循环发送:meterValue")
        transactionId = self.oc_transactionId_queue.get()
        while 1:
            if self.oc_connect_flag:  # 如果没有断连,连接状态还是True那么运行后面的发生电流电压
                try:
                    if not self.change_flag:
                        stop_charging_id = self.oc_stop_charging_id_queue.get()
                        # print("充电结束了,调用充电逻辑")
                        self.oc_send_message("Rejected", self.oc_requests_type3)
                        time.sleep(0.1)
                        self.oc_send_message("Accepted", self.oc_requests_type3, stop_charging_id=stop_charging_id)
                        time.sleep(0.1)
                        self.oc_send_message("Finishing", self.oc_requests_type2)
                        time.sleep(0.1)
                        # self.oc_send_message("Preparing", self.oc_requests_type2)
                        # time.sleep(0.1)
                        self.oc_send_message("StopTransaction", self.oc_requests_type2, idTag=idTag,
                                             transactionId=transactionId,
                                             change_value=str(value))  # 充电结束后发送结束信息,meterStop本次充电耗费了多少电量
                        time.sleep(random.randint(1800, 3600))
                        break
                    self.oc_send_message("MeterValues", self.oc_requests_type2, change_value=str(value),
                                         transactionId=transactionId,
                                         change_voltage=self.oc_change_voltage, change_electric=self.oc_change_electric,
                                         Power_Phase=self.oc_Power_Phase)
                    # print(f"MeterValues发送成功:电流{self.change_electric}:电压{self.change_voltage}:电量{str(value)}")
                    time.sleep(5)
                    value += int(round(self.oc_change_energy / 3600 * 5 * self.oc_Power_Phase, 1))
                except Exception as e:
                    self.oc_connect_flag = False
                    self.oc_clear_queue_dic()
                    # 如果充电过程中断连了,那么就把连接状态置为False,这里不尝试重连,交给心跳线程去重连
                    print(f"changer_ing:{e}")
            elif not self.oc_connect_flag:
                """如果充电的时候连接状态断开了,啥也不干,等10s,等心跳线程和recv线程重连就行了"""
                time.sleep(10)

    def oc_reSendMessage(self, in_data, msg_first_Flag):
        """重发消息函数"""
        msg_type = in_data[0]
        requests_type = in_data[1]
        old_message_flag = in_data[2]  # 需要重发消息的message_flag:202211250007xxxx
        new_message_flag = f"{time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))[2:]}{self.sn[-5:-1]}" \
                           f"{self.oc_time_flag:0>4}"
        self.oc_time_flag += 1
        args = in_data[3]
        kwargs = in_data[4]
        message = json.dumps(generate_msg(msg_type, requests_type, new_message_flag, *args, **kwargs))
        start_time = time.time()
        try:
            self.oc_ws.send(message)
            total_time = int((time.time() - start_time) * 1000)
            self.escalationReporting(total_time, "reSendMessage", response_length=len(message.encode("utf8")))
        except Exception as e:
            total_time = int((time.time() - start_time) * 1000)
            self.escalationReporting(total_time, "Send", response_length=0, exception=e)  # 上报日志
            self.oc_connect_flag = False  # 重发消息发生错误了就把连接状态置为False
            self.oc_clear_queue_dic()
        if msg_first_Flag is None:
            msg_first_Flag = old_message_flag
        if msg_type != "Accepted":  # 发送的Accepted消息因为服务端不会回的,所以只发
            self.oc_message_time_dic[new_message_flag] = [int(time.time() * 1000),  # 发送消息的时候注入消息时间和标志写入字典
                                                          msg_type,
                                                          msg_first_Flag,
                                                          (msg_type, requests_type, new_message_flag, args, kwargs)]
            print(f"reSendMessage   first_message_flag:{msg_first_Flag}   new_message_flag{new_message_flag}")
            print(message)

    def oc_send_message(self, msg_type, requests_type, *args, **kwargs):
        """发送消息"""
        message_flag = f"{time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))}{self.sn[-6:-1]}" \
                       f"{self.oc_time_flag:0>5}"
        self.oc_time_flag += 1
        message = json.dumps(generate_msg(msg_type, requests_type, message_flag, *args, **kwargs))
        if msg_type == "StartTransaction":
            print(message)
        start_time = time.time()
        try:
            self.oc_connect_event.wait()  # 没有连接成功阻塞在这里等待连接成功才发送消息
            self.oc_ws.send(message)
            total_time = int((time.time() - start_time) * 1000)
            self.escalationReporting(total_time, "Send", response_length=len(message.encode("utf8")))  # 上报日志
        except OSError as e:
            total_time = int((time.time() - start_time) * 1000)  # 发送消息失败了,说明 websocket断开了,重发,丢到重发的队列里
            self.escalationReporting(total_time, "Send", response_length=0, exception=e)  # 上报日志
        if msg_type != "Accepted":  # 发送的Accepted消息因为服务端不会回的,所以只发
            self.oc_message_time_dic[message_flag] = [int(time.time() * 1000),  # 发送消息的时候注入消息时间和标志写入字典
                                                      msg_type,
                                                      None,  # 重发标志,第一次send的时候就是None,后面这个标志都是第一次发消息的message_flag
                                                      (msg_type, requests_type, message_flag, args, kwargs)]
        else:  # 如果收到的消息是Accepted就记录一下Accepted的数量
            self.escalationReporting(0, "Accepted", response_length=len(message.encode("utf8")))  # 上报日志

    def oc_send_heartbeat(self):
        self.oc_send_message("Heartbeat", self.oc_requests_type2)

    def oc_while_heartbeat(self):
        """心跳线程,一直发心跳,死循环"""
        while 1:
            if self.oc_connect_flag:  # 当连接状态为True的时候,可以一直发心跳
                try:
                    self.oc_send_heartbeat()
                    time.sleep(10)
                except Exception as e:  # 当发送消息失败的时候
                    print(f"心跳线程发送消息出错了,错误信息为:{e}")
                    self.oc_connect_flag = False  # 发送消息发生错误了就把连接状态置为False
                    self.oc_clear_queue_dic()
            elif not self.oc_connect_flag:  # 连接状态为False了那么需要重新连接服务器
                time.sleep(2)

    def oc_processingTimeout(self):
        """处理超时协程,每5s处理一次"""
        # 1:遍历 self.message_time_dic = {} 这个消息字典 消息体类似这种 {'2022112618575860010001': [1669460278398,
        # 'BootNotification', None, ('BootNotification', 2, '2022112618575860010001', (), {})]}
        processingTime = int(time.time() * 1000)  # 处理时间,
        for messageFlag, body in list(self.oc_message_time_dic.items()):
            if processingTime - body[0] > outTimeLevel[2] and body[1] != "Heartbeat":
                try:
                    # 如果某些消息超过30s还没回复的,并且不是心跳消息,这里直接重发这些消息
                    self.oc_reSendMessage(body[3], body[2])  # 重发消息
                    del self.oc_message_time_dic[messageFlag]  # 消息重发了之后从self.message_time_dic删除这个已经重发的消息
                    if body[2] is None:
                        self.oc_outTime_dic[messageFlag] = 1
                    else:
                        self.oc_outTime_dic[body[2]] += 1
                except Exception as e:
                    print("processingTimeout 这里报错了,在try里面", e)
        # 2:判断当前 self.outTime_dic = {} 里的 统计的消息有大于 3条的了,如果有,那么断开ws连接,把桩连接状态置为未连接
        if self.oc_outTime_dic:
            if max(self.oc_outTime_dic.values()) >= 3:
                print("processingTimeout,桩因为三次消息超时重启了", self.oc_outTime_dic)
                self.oc_ws.close()  # 断开ws长链接
                self.oc_connect_flag = False  # 连接状态为False了那么需要重新连接服务器
                self.oc_clear_queue_dic()

    def oc_while_processingTimeout(self):
        while 1:
            """循环去执行超时处理的逻辑,5s执行一次"""
            if self.oc_connect_flag:
                self.oc_processingTimeout()
                time.sleep(2)
            elif not self.oc_connect_flag:  # 如果连接状态为False,表示断连了,这个协程再去重连服务器
                self.oc_Reconnection()  # 调用重连,2s重连一次
                time.sleep(2)

    def oc_recv_msg(self):
        """接受消息"""
        try:
            recv_message = self.oc_ws.recv()
        except websocket._exceptions.WebSocketConnectionClosedException as e:
            self.oc_connect_flag = False
            self.oc_clear_queue_dic()
            print(e, "recv_msg111111111")
        else:
            return recv_message

    def oc_total_time_bootRecvSend(self, recv_message):
        """处理收到消息的报告"""
        try:
            recv_message_flag = json.loads(recv_message)[1]
            if recv_message_flag in self.oc_message_time_dic and recv_message:  # 如果收到的消息在消息列表里,注入报告
                send_message_time = self.oc_message_time_dic[recv_message_flag][0]
                recv_message_time = int(time.time() * 1000)
                message_time = recv_message_time - send_message_time
                msg_type = self.oc_message_time_dic[recv_message_flag][1]
                msg_first_Flag = self.oc_message_time_dic[recv_message_flag][2]  # 消息发送的标志,None表示第一次发送
                self.escalationReporting(message_time, "Recv",
                                         response_length=len(recv_message.encode("utf8")))  # 注入报告
                self.escalationReporting(message_time, msg_type,
                                         response_length=len(recv_message.encode("utf8")))  # 注入报告
                if message_time <= outTimeLevel[0]:
                    self.escalationReporting(message_time, f"Recv In[0 - {outTimeLevel[0]}]ms",
                                             response_length=len(recv_message.encode("utf8")))  # 注入报告
                elif message_time <= outTimeLevel[1]:
                    self.escalationReporting(message_time, f"Recv In[{outTimeLevel[0]} - {outTimeLevel[1]}]ms",
                                             response_length=len(recv_message.encode("utf8")))  # 注入报告
                elif message_time <= outTimeLevel[2]:
                    self.escalationReporting(message_time, f"Recv In[{outTimeLevel[1]} - {outTimeLevel[2]}]ms",
                                             response_length=len(recv_message.encode("utf8")))  # 注入报告
                    # 小于30s的消息
                else:
                    self.escalationReporting(message_time, f"Recv In[{outTimeLevel[2]} - ∞]ms",
                                             response_length=len(recv_message.encode("utf8")))  # 注入报告
                    print(f"大于{outTimeLevel[2]}ms的消息:MessageFlag:{recv_message_flag}  SendTime:{send_message_time}   "
                          f"RecvTime:{recv_message_time}")  # 统计大于30s的消息
                    # 如果消息不是heartbeat,并且收到的时间大于30s,这里直接重发消息
                    # Heartbeat不重发
                    if msg_type != "Heartbeat":
                        self.oc_reSendMessage(self.oc_message_time_dic[recv_message_flag][3], msg_first_Flag)  # 重发消息

                        # 把整个消息的参数体丢队列里,比如  ('Preparing', 2, (), '2022112414550000010006', {})
                        if msg_first_Flag is None:  # msg_first_Flag为None,表示第一次重发
                            self.oc_outTime_dic[recv_message_flag] = 1
                        else:  # 如果不是第一次重发,表示消息重发 2,3次了,需要拿到他的第一个 msg_first_Flag
                            self.oc_outTime_dic[msg_first_Flag] += 1

                del self.oc_message_time_dic[recv_message_flag]  # 处理完了再删除消息
            else:  # 如果收到的消息没有在消息列表里,那么随便注入报告,收到消息的时间为0
                self.escalationReporting(0, "Recv")
        except json.decoder.JSONDecodeError as e:
            print(e, "哈哈", recv_message)

    def oc_clear_queue_dic(self):
        self.oc_message_time_dic = {}
        self.oc_outTime_dic = {}

    def oc_while_recv(self):
        while 1:
            if self.oc_connect_flag:  # 当连接状态为True的时候,可以一直收消息,进行后面的逻辑判断
                try:
                    self.oc_connect_event.wait()  # 没有连接成功前阻塞在这里,等待连接成功后做后面收发逻辑
                    recv_message = self.oc_recv_msg()
                    if recv_message:
                        if not recv_message.__contains__("command is not support"):
                            self.oc_total_time_bootRecvSend(recv_message)  # 收到的消息注入报告
                except Exception as e:
                    print(f"接受消息出错了,当前错误为:{e}")
                    # 接收消息出错的时候把recv_message置为空字符
                    recv_message = ""  # 收不到消息了
                    self.oc_connect_flag = False  # 接收消息出错了那么就把连接标志置为False
                    self.oc_clear_queue_dic()
                    raise e
                if recv_message is not None:  # 当recv_message不为None才去操作下面
                    if recv_message.__contains__("RemoteStartTransaction"):
                        # 启动充电
                        try:
                            self.oc_idTag_queue.put(json.loads(recv_message)[-1]["idTag"])
                        except Exception as e:
                            print(e, "00000000000000000000000000000000000000000000")
                        self.change_flag = True
                        gevent.spawn(self.oc_changer_ing)
                    elif recv_message.__contains__("TARIFF_RULE"):
                        """如果收到TARIFF_RULE 计费相关消息 获取seq并且 响应accept"""
                        try:
                            message_seq = json.loads(recv_message)[1]
                            self.oc_send_message("Accepted", self.oc_requests_type3, idTag=message_seq)
                        except Exception as e:
                            print(e, "TARIFF_RULE")
                    elif recv_message.__contains__('"data":{"getConfig":1}'):
                        """如果收到服务端的 getConfig消息,需要响应两个数据"""
                        try:
                            message_seq = json.loads(recv_message)[1]
                            self.oc_send_message("Accepted", self.oc_requests_type3, idTag=message_seq)  # 回一个config
                            time.sleep(1)
                            self.oc_send_message("Config", self.oc_requests_type2, pileSN=self.sn)  # 回一个config
                        except Exception as e:
                            print(e)
                    elif recv_message.__contains__('{"meterValuePerSec"}'):
                        """如果收到服务端的 meterValuePerSec 响应"""
                        try:
                            message_seq = json.loads(recv_message)[-1]["seq"]
                            self.oc_send_message("Accepted", self.oc_requests_type3, idTag=message_seq)  # 回一个config
                        except Exception as e:
                            print(e)
                    elif recv_message.__contains__('MeterValueSampleInterval'):
                        """如果收到服务端的 MeterValueSampleInterval 响应"""
                        try:
                            message_seq = json.loads(recv_message)[1]
                            self.oc_send_message("Accepted", self.oc_requests_type3, idTag=message_seq)  # 回一个config
                        except Exception as e:
                            print(e, "MeterValueSampleInterval")
                    elif recv_message.__contains__("RemoteStopTransaction"):
                        # 结束充电
                        self.change_flag = False
                        try:
                            self.oc_stop_charging_id_queue.put(json.loads(recv_message)[1])
                        except Exception as e:
                            print(e, "1111111111111111111111111111111111111")
                    elif recv_message.__contains__("transactionId") and recv_message.__contains__('"userId":"'):
                        # 获取交易id
                        try:
                            self.oc_transactionId_queue.put(json.loads(recv_message)[2]["transactionId"])
                            idTag = json.loads(recv_message)[1]
                            self.oc_send_message("Accepted", self.oc_requests_type3, idTag=idTag)
                        except Exception as e:
                            print(e, recv_message)
                    elif recv_message.__contains__('CHARGE_AMOUNT'):
                        try:
                            idTag = json.loads(recv_message)[1]
                            self.oc_send_message("Accepted", self.oc_requests_type3, idTag=idTag)
                        except Exception as e:
                            print(e, recv_message)
            elif not self.oc_connect_flag:  # 如果连接状态为False,表示断连了,睡10s,等待心跳线程重连就行了
                time.sleep(2)

oc_pile.py
oc_pile.py
from gevent import monkey;monkey.patch_all()
import time
import os
import sys
import random

from locust import User, TaskSet, task
from main_pile import MainPile
from eneGeneratePileAndUser import ene, scene, pile_type
from common import generateAcPile, generateDCPile
from eneGeneratePileAndUser import ge2 as ge


class MyTaskSet(TaskSet):

    def on_start(self):
        print("初始化用户")
        self.pile_data, self.index = self.user.ge_pile_data.__next__()
        print(self.pile_data)
        self.pileObj = MainPile(self.user.host, self.pile_data, self)

    @task
    def pile_change_status(self):
        if scene == "static":
            while 1:
                time.sleep(1000)
        elif scene == "timedCharging":
            type_lis = ["Preparing", "Available"]
            if self.index % 2 == 1:  # 如果桩是偶数的,就启动充电
                while 1:
                    self.pileObj.oc_pile_start_change()  # 开启主动充电
                    time.sleep(random.randint(180, 720))  # 充电时长
                    self.pileObj.oc_pile_stop_change()  # 停止充电
                    time.sleep(random.randint(12, 18))  # 休息2.3分钟后继续充电
            elif self.index % 2 == 0:  # 如果桩是奇数的,就正常启动充电
                while 1:
                    self.pileObj.oc_send_message(random.choice(type_lis), 2)  # 切换状态
                    time.sleep(random.randint(120, 180))
        elif scene == "SUSPENDEDEV_TIMEOUT":  # 暂停充电的场景
            type_lis = ["Preparing", "Available"]
            if self.index % 2 == 1:  # 如果是标志需要充电的桩,就去循环充电,一直循环
                while 1:
                    time.sleep(random.randint(5, 30))  # 随机延迟5-30s后开启充电
                    self.pileObj.oc_pile_start_change()  # 开启主动充电
                    time.sleep(random.randint(1800, 3600))  # 充电30-60分钟
                    if self.index % 4 == 1:  # 如果能整除4,那么这部分开启暂停充电
                        self.pileObj.Pause_charging_flag = True  # 开启暂停充电
                        self.pileObj.oc_send_message("SuspendedEV", self.pileObj.oc_requests_type2, pile_connectorId=1)
                        time.sleep(random.randint(3600, 4800))  # 暂停1小时或者1小时20分钟
                        self.pileObj.Pause_charging_flag = False  # 取消暂停
                        time.sleep(100)  # 再充电100s后桩停止充电
                    self.pileObj.oc_pile_stop_change()  # 停止充电
                    time.sleep(random.randint(120, 180))  # 休息2.3分钟后继续充电
            elif self.index % 2 == 0:  # 如果不是需要充电的就去2-3分钟切换一次状态
                while 1:
                    self.pileObj.oc_send_message(random.choice(type_lis), 2)  # 切换状态
                    time.sleep(random.randint(120, 180))
        elif scene == "test":  # 调试模式
            while 1:
                time.sleep(random.randint(1200, 1800))  # 充电时长
                out_num = random.randint(1, 10)
                if out_num == 5:  # 切换状态Fault
                    self.pileObj.oc_send_message("Faulted", self.pileObj.oc_requests_type2)  # 进入故障
                    time.sleep(random.randint(1200, 1800))
                    self.pileObj.oc_send_message("Available", self.pileObj.oc_requests_type2)  # 开始可用

    def on_stop(self):
        print("执行完后的步骤")


class WebSitUser(User):
    host = ene  # 指定主机地址   "china_test" , "England_test"
    if pile_type == "AC":
        ge_pile_data = generateAcPile(ge)
    elif pile_type == "DC":
        ge_pile_data = generateDCPile(ge)
    tasks = [MyTaskSet]
    min_wait = 100
    max_wait = 200


if __name__ == "__main__":
    current_dir = os.getcwd()
    sys.path.insert(0, current_dir)
    os.environ['NO_PROXY'] = 'stackoverflow.com'
    os.system("locust -f worker2.py")

worker1.py
worker1.py

25:MQTT长链接性能压测脚本编写思路【Locust+Python】

broker = ""  # 
port = 0       # 1883
topic = ""  # "testtopic/1"


def generateSN(in_range):
    for index in range(in_range[0], in_range[1]):
        pile_sn = f'DE1240BTEST{index:0>6}J'
        yield pile_sn, index


ge = [1, 999999]
# ge1 = [1, 20000]
# ge2 = [20000, 40000]
# ge3 = [40000, 80000]
# ge4 = [80000, 100000]
# ge5 = [100000, 120000]
# ge6 = [120000, 140000]
# ge7 = [140000, 160000]
# ge8 = [160000, 180000]
# ge9 = [180000, 200000]
# ge10 = [200000, 220000]

ge1 = [220000, 240000]
ge2 = [240000, 260000]
ge3 = [260000, 280000]
ge4 = [280000, 300000]

config.py
config.py
from gevent import monkey;monkey.patch_all()
import time
from paho.mqtt import client as mqtt_client
import random
from locust import task
from locust import SequentialTaskSet
from locust import User

from config import broker, port, topic, ge, generateSN


# def on_connect(client, userdata, flags, rc):
#     if rc == 0:
#         print("Connected to MQTT Broker!")
#     else:
#         print("Failed to connect, return code %dn", rc)


class MyTaskSet(SequentialTaskSet):
    def on_start(self):
        print("初始化用户")
        self.pile_sn, self.index = self.user.ge_pile_sn.__next__()
        print(broker)
        try:
            self.mqtt_client = mqtt_client.Client(f'python-mqtt-{self.pile_sn}')
            self.mqtt_client.connect(broker, port, 0)
            self.user.environment.events.request.fire(request_type="MQTT",
                                                      name="Connect",
                                                      response_time=0,
                                                      response_length=0,
                                                      response=None,
                                                      context=None,
                                                      exception=None)

            print(self.mqtt_client)
        except Exception as e:
            self.user.environment.events.request.fire(request_type="MQTT",
                                                      name="Connect",
                                                      response_time=0,
                                                      response_length=0,
                                                      response=None,
                                                      context=None,
                                                      exception=e)

    @task
    def mainTest(self):
        in_topic = topic.replace("{sn}", self.pile_sn)
        while 1:
            try:
                result = self.mqtt_client.publish(in_topic, f"{self.pile_sn}:messages:{random.randint(1, 100000000)}")
                status = result[0]
                if status == 0:
                    self.user.environment.events.request.fire(request_type="MQTT",
                                                              name="send",
                                                              response_time=0,
                                                              response_length=0,
                                                              response=None,
                                                              context=None,
                                                              exception=None)
                time.sleep(10)
            except Exception as e:
                self.user.environment.events.request.fire(request_type="MQTT",
                                                          name="send",
                                                          response_time=0,
                                                          response_length=0,
                                                          response=None,
                                                          context=None,
                                                          exception=e)
                time.sleep(10)
                self.mqtt_client.connect(broker, port)

    def on_stop(self):
        print("执行完后的步骤")


class WebSitUser(User):
    tasks = [MyTaskSet]
    host = broker
    ge_pile_sn = generateSN(ge)


if __name__ == '__main__':
    import os
    import sys

    current_dir = os.getcwd()
    sys.path.insert(0, current_dir)
    os.environ['NO_PROXY'] = 'stackoverflow.com'
    os.system("locust -f master.py")
master.py
master.py
from gevent import monkey;monkey.patch_all()
import time
from paho.mqtt import client as mqtt_client
import random
from locust import task
from locust import SequentialTaskSet
from locust import User

from config import broker, port, topic, ge1, generateSN


# def on_connect(client, userdata, flags, rc):
#     if rc == 0:
#         print("Connected to MQTT Broker!")
#     else:
#         print("Failed to connect, return code %dn", rc)


class MyTaskSet(SequentialTaskSet):
    def on_start(self):
        print("初始化用户")
        self.pile_sn, self.index = self.user.ge_pile_sn.__next__()
        print(broker)
        try:
            self.mqtt_client = mqtt_client.Client(f'python-mqtt-{self.pile_sn}')
            self.mqtt_client.connect(broker, port, 0)
            self.user.environment.events.request.fire(request_type="MQTT",
                                                      name="Connect",
                                                      response_time=0,
                                                      response_length=0,
                                                      response=None,
                                                      context=None,
                                                      exception=None)

            print(self.mqtt_client)
        except Exception as e:
            self.user.environment.events.request.fire(request_type="MQTT",
                                                      name="Connect",
                                                      response_time=0,
                                                      response_length=0,
                                                      response=None,
                                                      context=None,
                                                      exception=e)

    @task
    def mainTest(self):
        in_topic = topic.replace("{sn}", self.pile_sn)
        while 1:
            try:
                result = self.mqtt_client.publish(in_topic, f"{self.pile_sn}:messages:{random.randint(1, 100000000)}")
                status = result[0]
                if status == 0:
                    self.user.environment.events.request.fire(request_type="MQTT",
                                                              name="send",
                                                              response_time=0,
                                                              response_length=0,
                                                              response=None,
                                                              context=None,
                                                              exception=None)
                time.sleep(10)
            except Exception as e:
                self.user.environment.events.request.fire(request_type="MQTT",
                                                          name="send",
                                                          response_time=0,
                                                          response_length=0,
                                                          response=None,
                                                          context=None,
                                                          exception=e)
                time.sleep(10)
                self.mqtt_client.connect(broker, port)

    def on_stop(self):
        print("执行完后的步骤")


class WebSitUser(User):
    tasks = [MyTaskSet]
    host = broker
    ge_pile_sn = generateSN(ge1)


if __name__ == '__main__':
    import os
    import sys

    current_dir = os.getcwd()
    sys.path.insert(0, current_dir)
    os.environ['NO_PROXY'] = 'stackoverflow.com'
    os.system("locust -f worker1.py")

worker1.py
worker1.py

26:HTTP性能压测脚本编写思路【Locust+Python】

from gevent import monkey;monkey.patch_all()
import requests
import os
import sys
from functools import wraps
import time

from locust import User, TaskSet, task


class EneGatewayHost:
    china_test = ""  # 中国测试
    England_test = ""  # 英国测试
    USA_test = ""  # 美西测试


def reporter():
    def bar(func):
        @wraps(func)
        def inner(*args, **kwargs):
            user_obj = args[0]
            start_time = time.time()
            try:
                res = func(*args, **kwargs)
                total_time = int((time.time() - start_time) * 1000)
                if res["code"] == 200:
                    user_obj.reportObj(request_type="HTTP",
                                       name=func.__name__,
                                       response_time=total_time,
                                       response_length=len(str(res).encode("utf8")),
                                       response=None,
                                       context=None,
                                       exception=None)
                else:
                    user_obj.reportObj(request_type="HTTP",
                                       name=func.__name__,
                                       response_time=total_time,
                                       response_length=len(str(res).encode("utf8")),
                                       response=None,
                                       context=None,
                                       exception=res["error"])
                return res
            except Exception as e:
                total_time = int((time.time() - start_time) * 1000)
                user_obj.reportObj(request_type="HTTP",
                                   name=func.__name__,
                                   response_time=total_time,
                                   response_length=0,
                                   response=None,
                                   context=None,
                                   exception=e)

        return inner

    return bar


class TestUser:
    def __init__(self, in_ene, locustUser):
        self.reportObj = locustUser.user.environment.events.request.fire  # 注入报告的
        self.gateway = getattr(EneGatewayHost, in_ene)
        self.headers = None
        self.login_get_token()

    @reporter()
    def login_get_token(self):
        url = f"http://{self.gateway}xxxxxx"
        reps = requests.get(url=url, ).json()
        self.headers = {
            "Authorization": reps['data']
        }
        return reps

    @reporter()
    def getBillDetail(self):
        url = f"http://{self.gateway}/api/pile-bill-app/bill/getBillDetail"
        payload = {
            "busId": "2000116872",
            "evseSn": "AE0022TEST9F62409D"
        }
        reps = requests.post(url, headers=self.headers, json=payload)
        return reps.json()


class MyTaskSet(TaskSet):

    def on_start(self):
        print("初始化用户")
        self.apiObj = TestUser(self.user.host,  self)

    @task
    def pile_change_status(self):
        self.apiObj.getBillDetail()

    def on_stop(self):
        print("执行完后的步骤")


class WebSitUser(User):
    host = "china_test"  # 指定主机地址   "china_test" , "England_test"
    tasks = [MyTaskSet]
    min_wait = 100
    max_wait = 200


if __name__ == "__main__":
    current_dir = os.getcwd()
    sys.path.insert(0, current_dir)
    os.environ['NO_PROXY'] = 'stackoverflow.com'
    os.system("locust -f locust_get_bill_detail.py")

locust_get_bill_detail.py
locust_get_bill_detail.py

 

posted @ 2022-04-13 04:48  至高无上10086  阅读(2899)  评论(1编辑  收藏  举报