智能充电云管理系统:为新能源电动车与电单车提供定制解决方案

一 系统简介

智能充电(新能源电动车,电单车)云管理系统 是一套能够实现对充电站/桩的实时通讯、状态监控、故障检测、运营分析、数据统计、策略设置的智能化多任务管理系统。

二 平台概览

智能充电云管理系统  
https://chongdianzhuang.itgcs.tech/  账号:demo 密码:demo123

三 通信协议

  • Http+SSL协议:运维管理平台
  • TCP协议:与充电桩通信
  • Websocket+SSL协议:与微信小程序实时双向通信
  • 四 支持的充电桩

  • 新能源汽车充电:包括直流桩和交流桩,云快充平台协议V1.6
  • 两轮电单车充电
  • 桩企业私有协议等(定制开发)
  • 五 运行环境

    Windows 或 类Linux系统,Mysql,Redis,RabbitMQ等服务

    六 通信代码

    1 TCP Server:与充电桩通信,CRC校验

    func TCPServer() {
    	//以下是TCP Server
    	listenAddrs := []string{
    		GlobalConfig.TCPAddr1,
    		GlobalConfig.TCPAddr2,
    	}
    
    	for _, addr := range listenAddrs {
    		go func(addr string) {
    			listener, err := net.Listen("tcp", addr)
    			if err != nil {
    				fmtPrintf("[TCP] Failed to listen on %s: %v", addr, err)
    				return
    			}
    			defer listener.Close()
    
    			fmtPrintf("[TCP] Server listening on %s", addr)
    
    			for {
    				conn, err := listener.Accept()
    				if err != nil {
    					fmtPrintf("[TCP] Error accepting connection: %v", err)
    					continue
    				}
    
    				go handleConn(conn)
    			}
    		}(addr)
    	}
    }

    1.1 充电桩登录消息定义

    
    // 0x01 MsgLogin 登录消息
    type MsgLogin struct {
    	MsgHeader                // 继承 MsgHeader
    	ChargePileID    [7]byte  `json:"ChargePileID"`    // 充电桩ID
    	ChargePileType  byte     `json:"ChargePileType"`  // 充电桩类型
    	ChargeShotNum   byte     `json:"ChargeShotNum"`   // 充电枪数量
    	ProtocolVersion byte     `json:"ProtocolVersion"` // 协议版本
    	ProgramVersion  [8]byte  `json:"ProgramVersion"`  // 程序版本
    	NetType         byte     `json:"NetType"`         // 网络类型
    	SIM             [10]byte `json:"SIM"`             // SIM卡信息
    	NetCarrier      byte     `json:"NetCarrier"`      // 网络运营商
    	// CheckSum        [2]byte  `json:"CheckSum"`        // 校验和
    }
    
    // 0x02 MsgLoginRsp 登录响应消息
    type MsgLoginRsp struct {
    	MsgHeader            // 继承 MsgHeader
    	ChargePileID [7]byte `json:"ChargePileID"` // 充电桩ID
    	Ret          byte    `json:"Ret"`          // 登录结果
    }

    1.2 充电桩启停充电消息定义

    
    // 0x33
    type MsgStartChargeRspToServer struct {
    	MsgHeader              // 继承 MsgHeader
    	TransactionID [16]byte `json:"TransactionID"` // 交易流水号
    	ChargePileID  [7]byte  `json:"ChargePileID"`  // 桩编号
    	ChargeGunID   byte     `json:"ChargeGunID"`   // 枪号
    	Ret           byte     `json:"Ret"`           // 启动结果 0x00失败 0x01成功
    	FailCode      byte     `json:"FailCode"`      // 失败原因
    }
    
    // 0x34
    type MsgStartChargeToDevice struct {
    	MsgHeader               // 继承 MsgHeader
    	TransactionID  [16]byte `json:"TransactionID"`  // 交易流水号
    	ChargePileID   [7]byte  `json:"ChargePileID"`   // 桩编号
    	ChargeGunID    byte     `json:"ChargeGunID"`    // 枪号
    	LogicalCardID  [8]byte  `json:"LogicalCardID"`  // 显示在屏幕上,不足补零,逻辑卡号为卡面印刷卡号
    	PhysicalCardID [8]byte  `json:"PhysicalCardID"` // 不足补零,桩与平台交互需使用的物理卡号
    	Balance        [4]byte  `json:"Balance"`        // 账户余额,保留到小数点两位
    }
    
    // 0x35
    type MsgStopChargeRspToServer struct {
    	MsgHeader            // 继承 MsgHeader
    	ChargePileID [7]byte `json:"ChargePileID"` // 桩编号
    	ChargeGunID  byte    `json:"ChargeGunID"`  // 枪号
    	Ret          byte    `json:"Ret"`          // 启动结果 0x00失败 0x01成功
    	FailCode     byte    `json:"FailCode"`     // 0x00 无 0x01 设备编号不匹配 0x02 枪未处于充电状态 0x03 其他
    }
    
    // 0x36
    type MsgStopChargeToDevice struct {
    	MsgHeader            // 继承 MsgHeader
    	ChargePileID [7]byte `json:"ChargePileID"` // 桩编号
    	ChargeGunID  byte    `json:"ChargeGunID"`  // 枪号
    }
    

    1.3 ModBusCRC校验实现

    
    func ModbusCRC(pData []byte, lenData byte) (byte, byte) {
    	var (
    		crcHi byte = 0xFF
    		crcLo byte = 0xFF
    		idx   byte
    	)
    
    	crchi := []byte{
    		0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
    		0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
    		0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
    		0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40,
    		0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1,
    		0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41,
    		0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1,
    		0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
    		0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
    		0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40,
    		0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1,
    		0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40,
    		0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
    		0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40,
    		0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
    		0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40,
    		0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
    		0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
    		0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
    		0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
    		0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
    		0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40,
    		0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1,
    		0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
    		0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
    		0x80, 0x41, 0x00, 0xc1, 0x81, 0x40}
    
    	crclow := []byte{
    		0x00, 0xc0, 0xc1, 0x01, 0xc3, 0x03, 0x02, 0xc2, 0xc6, 0x06,
    		0x07, 0xc7, 0x05, 0xc5, 0xc4, 0x04, 0xcc, 0x0c, 0x0d, 0xcd,
    		0x0f, 0xcf, 0xce, 0x0e, 0x0a, 0xca, 0xcb, 0x0b, 0xc9, 0x09,
    		0x08, 0xc8, 0xd8, 0x18, 0x19, 0xd9, 0x1b, 0xdb, 0xda, 0x1a,
    		0x1e, 0xde, 0xdf, 0x1f, 0xdd, 0x1d, 0x1c, 0xdc, 0x14, 0xd4,
    		0xd5, 0x15, 0xd7, 0x17, 0x16, 0xd6, 0xd2, 0x12, 0x13, 0xd3,
    		0x11, 0xd1, 0xd0, 0x10, 0xf0, 0x30, 0x31, 0xf1, 0x33, 0xf3,
    		0xf2, 0x32, 0x36, 0xf6, 0xf7, 0x37, 0xf5, 0x35, 0x34, 0xf4,
    		0x3c, 0xfc, 0xfd, 0x3d, 0xff, 0x3f, 0x3e, 0xfe, 0xfa, 0x3a,
    		0x3b, 0xfb, 0x39, 0xf9, 0xf8, 0x38, 0x28, 0xe8, 0xe9, 0x29,
    		0xeb, 0x2b, 0x2a, 0xea, 0xee, 0x2e, 0x2f, 0xef, 0x2d, 0xed,
    		0xec, 0x2c, 0xe4, 0x24, 0x25, 0xe5, 0x27, 0xe7, 0xe6, 0x26,
    		0x22, 0xe2, 0xe3, 0x23, 0xe1, 0x21, 0x20, 0xe0, 0xa0, 0x60,
    		0x61, 0xa1, 0x63, 0xa3, 0xa2, 0x62, 0x66, 0xa6, 0xa7, 0x67,
    		0xa5, 0x65, 0x64, 0xa4, 0x6c, 0xac, 0xad, 0x6d, 0xaf, 0x6f,
    		0x6e, 0xae, 0xaa, 0x6a, 0x6b, 0xab, 0x69, 0xa9, 0xa8, 0x68,
    		0x78, 0xb8, 0xb9, 0x79, 0xbb, 0x7b, 0x7a, 0xba, 0xbe, 0x7e,
    		0x7f, 0xbf, 0x7d, 0xbd, 0xbc, 0x7c, 0xb4, 0x74, 0x75, 0xb5,
    		0x77, 0xb7, 0xb6, 0x76, 0x72, 0xb2, 0xb3, 0x73, 0xb1, 0x71,
    		0x70, 0xb0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
    		0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9c, 0x5c,
    		0x5d, 0x9d, 0x5f, 0x9f, 0x9e, 0x5e, 0x5a, 0x9a, 0x9b, 0x5b,
    		0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4b, 0x8b,
    		0x8a, 0x4a, 0x4e, 0x8e, 0x8f, 0x4f, 0x8d, 0x4d, 0x4c, 0x8c,
    		0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
    		0x43, 0x83, 0x41, 0x81, 0x80, 0x40}
    
    	for ; lenData > 0; lenData-- {
    		idx = crcHi ^ pData[0]
    		pData = pData[1:]
    		crcHi = crcLo ^ crchi[idx]
    		crcLo = crclow[idx]
    	}
    
    	// return uint16(crcHi)<<8 | uint16(crcLo)
    	return crcHi, crcLo
    }

     2 Websocket Server + SSL证书:提供小程序端通信服务

    func WssServer() {
    	http.HandleFunc("/websocket", handleClient)
    	fmtPrintf("[WebSocket] Server listening on %s", GlobalConfig.WebSocketAddr)
    	fmtPrintf("[WebSocket] Cert: %s, Key: %s ", GlobalConfig.ProtocolCert, GlobalConfig.ProtocolPrivateKey)
    	err := http.ListenAndServeTLS(GlobalConfig.WebSocketAddr, GlobalConfig.ProtocolCert, GlobalConfig.ProtocolPrivateKey, nil)
    	if err != nil {
    		fmtPrintf("WebSocket server err:%v", err)
    	}
    	go wsClientsKeepalive()
    }

    3 Websocket Client 小程序端:含断线重连,保证客户端一个始终一个连接

    // utils/webSocketManager.js
    import util from 'util.js';
    const webSocketManager = (() => {
      let socket;
      let reconnectTimer; // 用于存储定时器ID,用于重连
      const MAX_RECONNECT_TIMES = 10; // 最大重连次数
      const RECONNECT_INTERVAL = 3000; // 重连间隔时间,单位毫秒
    
      const initWebSocket = (url) => {
          util.log('WebSocket【initWebSocket】 连接:', url);
          if (socket && socket.readyState === 1) {
              util.log('WebSocket【initWebSocket】已连接,无需重新初始化 ');
              return;
          }
      
          socket = wx.connectSocket({ url: url });
          
          socket.onOpen(() => {
            util.log('WebSocket 【onOpen】');
            // 连接成功后发送login
            wx.getStorage({
              key: "openid",
              success(res) {
                if(res.data == null) {
                  util.log("initWebSocket close socket openid null ")
                  close()
                }
                let dataObj = {  
                  cmd: 'login',  
                  src: 'wx',
                  dst: 'wx',
                  uid:  res.data,  
                  msg: null,  
                }; 
                send(dataObj);
              },
              fail (res) {   
                util.log("initWebSocket close socket wx.getStorage err: ", res)
                close()
              },
            })
          });
    
          socket.onMessage((res) => {
              util.log('WebSocket 【onMessage】:', res.data);
              // 在这里处理接收到的消息
              var obj = JSON.parse(res.data)
    
              // if (obj.cmd == "wx_requestPayment"){
              //   var msg = JSON.parse(obj.msg)
              //   wx.requestPayment({
              //     "timeStamp": msg.timeStamp,
              //     "nonceStr": msg.nonceStr,
              //     "package": msg.package,
              //     "signType": msg.signType,
              //     "paySign": msg.paySign,
              //     "success":function(res){
              //       util.log("requestPayment ok")
              //     },
              //     "fail":function(res){
              //       util.log("requestPayment fail",res)
              //     }
              //   })
              // }
          });
    
          socket.onError((err) => {
              // 这里可以处理错误情况,例如关闭socket
              util.log('WebSocket 【onError】连接已已出错,',err);
          });
    
          socket.onClose(() => {
            // 这里可以处理错误情况,例如关闭socket
            util.log('WebSocket 【onClose】连接已关闭,尝试重连...');
            // 当连接关闭时,也尝试重连
            if (!reconnectTimer && socket.readyState !== 1) {
                reconnectTimer = setInterval(() => {
                    if (socket.readyState !== 1) {
                        initWebSocket(url);
                    } else {
                        clearInterval(reconnectTimer);
                        reconnectTimer = null;
                    }
                }, RECONNECT_INTERVAL);
            }
        });
      };
    
      const send = (data) => {
          if (socket && socket.readyState === 1) {
              var messageStr = JSON.stringify(data);
              util.log('WebSocket 【send】:', messageStr);
              socket.send({
                data: messageStr
              });
          } else {
              util.warn('WebSocket未连接,无法发送消息');
          }
      };
    
      const close = () => {
          if (socket) {
              socket.close();
              // 清除重连定时器
              if (reconnectTimer) {
                  clearInterval(reconnectTimer);
                  reconnectTimer = null;
              }
          }
      };
    
      return {
          initWebSocket,
          send,
          close,
      };
    })();
    
    export default webSocketManager;

    4 HTTP+SSL证书服务:提供小程端通信服务

    
    func HttpsServer() {
    	// 加载证书和私钥
    	cert, err := tls.LoadX509KeyPair(GlobalConfig.ProtocolCert, GlobalConfig.ProtocolPrivateKey)
    	if err != nil {
    		fmtPrintf("HttpsServer %v", err)
    	}
    
    	// 配置TLS
    	config := &tls.Config{
    		Certificates: []tls.Certificate{cert},
    	}
    
    	// 创建一个新的ServerMux实例(默认情况下,http.HandleFunc就是使用这个实例)
    	mux := http.NewServeMux()
    
    	// 注册处理程序
    	// mux.HandleFunc("/", handler)
    	mux.HandleFunc("/wx_notify_url", handle_wx_notify_url)
    	// 监听端口并启动服务器
    	srv := &http.Server{
    		Addr:      GlobalConfig.HttpsAddr, // 或者使用其他端口,但443是HTTPS的标准端口
    		Handler:   mux,
    		TLSConfig: config,
    	}
    
    	fmtPrintf("[Https] Server listening on %s", GlobalConfig.HttpsAddr)
    	fmtPrintf("[Https] WXNotifyURL: %s", GlobalConfig.WXNotifyURL)
    	if err := srv.ListenAndServeTLS("", ""); err != nil { // 空字符串意味着使用上面定义的config中的证书和私钥
    		log.Fatal(err)
    	}
    }

    作者:YYDataV软件开发

    物联沃分享整理
    物联沃-IOTWORD物联网 » 智能充电云管理系统:为新能源电动车与电单车提供定制解决方案

    发表回复