Kaynağa Gözat

Add MeterService

YueYunyun 2 yıl önce
ebeveyn
işleme
29bb8cc9ee
85 değiştirilmiş dosya ile 7139 ekleme ve 0 silme
  1. 28 0
      SERVER/Meter_Service/config.yaml
  2. 41 0
      SERVER/Meter_Service/controller/app.go
  3. 77 0
      SERVER/Meter_Service/controller/device.go
  4. 39 0
      SERVER/Meter_Service/controller/dto/app.go
  5. 87 0
      SERVER/Meter_Service/controller/dto/device.go
  6. 11 0
      SERVER/Meter_Service/controller/index.go
  7. 33 0
      SERVER/Meter_Service/core/api/api.go
  8. 68 0
      SERVER/Meter_Service/core/config/config.go
  9. 10 0
      SERVER/Meter_Service/core/config/loggerConfig.go
  10. 12 0
      SERVER/Meter_Service/core/config/mysqlConfig.go
  11. 8 0
      SERVER/Meter_Service/core/config/redisConfig.go
  12. 11 0
      SERVER/Meter_Service/core/config/vberConfig.go
  13. 102 0
      SERVER/Meter_Service/core/constant/constant.go
  14. 103 0
      SERVER/Meter_Service/core/db/db.go
  15. 122 0
      SERVER/Meter_Service/core/db/mysql/mysql.go
  16. 23 0
      SERVER/Meter_Service/core/db/redis.go
  17. 51 0
      SERVER/Meter_Service/core/db/sqlite/sqlite.go
  18. 132 0
      SERVER/Meter_Service/core/log.go
  19. 148 0
      SERVER/Meter_Service/core/logger/logger.go
  20. 10 0
      SERVER/Meter_Service/core/modbus/.travis.yml
  21. 26 0
      SERVER/Meter_Service/core/modbus/LICENSE
  22. 78 0
      SERVER/Meter_Service/core/modbus/README.md
  23. 52 0
      SERVER/Meter_Service/core/modbus/api.go
  24. 228 0
      SERVER/Meter_Service/core/modbus/asciiclient.go
  25. 76 0
      SERVER/Meter_Service/core/modbus/asciiclient_test.go
  26. 602 0
      SERVER/Meter_Service/core/modbus/client.go
  27. 72 0
      SERVER/Meter_Service/core/modbus/crc.go
  28. 19 0
      SERVER/Meter_Service/core/modbus/crc_test.go
  29. 33 0
      SERVER/Meter_Service/core/modbus/lrc.go
  30. 19 0
      SERVER/Meter_Service/core/modbus/lrc_test.go
  31. 93 0
      SERVER/Meter_Service/core/modbus/modbus.go
  32. 212 0
      SERVER/Meter_Service/core/modbus/rtuclient.go
  33. 98 0
      SERVER/Meter_Service/core/modbus/rtuclient_test.go
  34. 102 0
      SERVER/Meter_Service/core/modbus/serial.go
  35. 36 0
      SERVER/Meter_Service/core/modbus/serial_test.go
  36. 285 0
      SERVER/Meter_Service/core/modbus/tcpclient.go
  37. 118 0
      SERVER/Meter_Service/core/modbus/tcpclient_test.go
  38. 24 0
      SERVER/Meter_Service/core/modbus/test/README.md
  39. 49 0
      SERVER/Meter_Service/core/modbus/test/asciiclient_test.go
  40. 153 0
      SERVER/Meter_Service/core/modbus/test/client.go
  41. 31 0
      SERVER/Meter_Service/core/modbus/test/common.go
  42. 107 0
      SERVER/Meter_Service/core/modbus/test/commw32/commw32.c
  43. 83 0
      SERVER/Meter_Service/core/modbus/test/commw32/commw32.go
  44. 49 0
      SERVER/Meter_Service/core/modbus/test/rtuclient_test.go
  45. 46 0
      SERVER/Meter_Service/core/modbus/test/tcpclient_test.go
  46. 63 0
      SERVER/Meter_Service/core/tcpserver/gontpd.go
  47. 393 0
      SERVER/Meter_Service/core/tcpserver/tcpserver.go
  48. 78 0
      SERVER/Meter_Service/core/utils/httpHelper/HTTPClientHelper.go
  49. 112 0
      SERVER/Meter_Service/core/utils/security.go
  50. 63 0
      SERVER/Meter_Service/core/utils/string.go
  51. 56 0
      SERVER/Meter_Service/core/utils/util.go
  52. 62 0
      SERVER/Meter_Service/data/device.go
  53. 85 0
      SERVER/Meter_Service/data/dtu.go
  54. 13 0
      SERVER/Meter_Service/dataStruct/clientState.go
  55. 104 0
      SERVER/Meter_Service/dataStruct/collect.go
  56. 110 0
      SERVER/Meter_Service/dataStruct/dtuConfig.go
  57. 88 0
      SERVER/Meter_Service/dataStruct/dtuDevice.go
  58. 47 0
      SERVER/Meter_Service/dataStruct/map.go
  59. 105 0
      SERVER/Meter_Service/dataStruct/mapAppApi.go
  60. 103 0
      SERVER/Meter_Service/dataStruct/mapClientState.go
  61. 56 0
      SERVER/Meter_Service/dataStruct/mapDtu.go
  62. 27 0
      SERVER/Meter_Service/dataStruct/mapInterface.go
  63. 74 0
      SERVER/Meter_Service/dataStruct/meterCalcParam.go
  64. 7 0
      SERVER/Meter_Service/dataStruct/parsingData.go
  65. 79 0
      SERVER/Meter_Service/dataStruct/struct.go
  66. 24 0
      SERVER/Meter_Service/database/appApi/app.go
  67. 8 0
      SERVER/Meter_Service/database/appApi/appApi.go
  68. 86 0
      SERVER/Meter_Service/database/appApi/db.go
  69. 54 0
      SERVER/Meter_Service/database/meterCalcParam/db.go
  70. 17 0
      SERVER/Meter_Service/database/meterCalcParam/meterCalcParam.go
  71. BIN
      SERVER/Meter_Service/database/vber.db
  72. 66 0
      SERVER/Meter_Service/go.mod
  73. 266 0
      SERVER/Meter_Service/go.sum
  74. 56 0
      SERVER/Meter_Service/main.go
  75. 25 0
      SERVER/Meter_Service/meter/proto/proto.go
  76. 123 0
      SERVER/Meter_Service/service/downStreamService/collect.go
  77. 346 0
      SERVER/Meter_Service/service/downStreamService/proto/acrel/acrel.go
  78. 136 0
      SERVER/Meter_Service/service/downStreamService/proto/report/report.go
  79. 14 0
      SERVER/Meter_Service/service/downStreamService/report.go
  80. 159 0
      SERVER/Meter_Service/service/downStreamService/service.go
  81. 59 0
      SERVER/Meter_Service/service/reportService/service.go
  82. 70 0
      SERVER/Meter_Service/service/rtuService/rtuTcp.go
  83. 42 0
      SERVER/Meter_Service/service/webService/middel/middel.go
  84. 27 0
      SERVER/Meter_Service/service/webService/router/router.go
  85. 29 0
      SERVER/Meter_Service/service/webService/service.go

+ 28 - 0
SERVER/Meter_Service/config.yaml

@@ -0,0 +1,28 @@
+vber:
+  name: MeterService
+  description: 电表采集服务
+  webPort: 14000 #web服务端口
+  cmdPort: 14001 #命令端口,也就是控制端口
+  fwdPort: 14002 #数据转发端口
+  mode: debug
+mysql:
+  host:
+  port:
+  username:
+  password:
+  name:
+  maxIdleConns: 10
+  maxOpenConns: 2
+  log: true
+redis:
+  host: 192.168.0.82
+  port: 6379
+  password: Iwb-2023
+  database: 0
+logger:
+  filePath: "./_logs"
+  fileFormat: "%Y%m%d" # 文件格式
+  level: debug # 日志级别,info,debug,error
+  logType: console # 日志输出类型,json,console
+  fileType: level # one, level 单文件存储还是以level级别存储
+  maxSaveDays: 3 # 保存天数

+ 41 - 0
SERVER/Meter_Service/controller/app.go

@@ -0,0 +1,41 @@
+package controller
+
+import (
+	"MeterService/controller/dto"
+	"MeterService/core/api"
+	"MeterService/core/utils"
+	"MeterService/dataStruct"
+	"MeterService/database/appApi"
+
+	"github.com/gin-gonic/gin"
+)
+
+func RegisterApp(c *gin.Context) {
+	app := &dto.App{}
+	if err := c.BindJSON(app); err != nil {
+		api.Fail(c, err.Error())
+		return
+	}
+	db := appApi.NewAppApiDb()
+	app.AppSecret = utils.GenerateRandomKey(32, 2)
+	err := db.AddOrUpdate(app.ToDbApp())
+	if err != nil {
+		api.Fail(c, err.Error())
+		return
+	}
+	apiMap := dataStruct.NewMapAppApi()
+	apiMap.RemoveAppId(app.AppId)
+	for _, v := range app.Apis {
+		apiMap.Add(v.Type, &appApi.AppApi{
+			AppId: app.AppId,
+			Type:  v.Type,
+			Host:  app.Host,
+			Url:   v.Url,
+		})
+	}
+	resp := &dto.RespApp{
+		AppId:     app.AppId,
+		AppSecret: app.AppSecret,
+	}
+	api.Ok2(c, resp)
+}

+ 77 - 0
SERVER/Meter_Service/controller/device.go

@@ -0,0 +1,77 @@
+package controller
+
+import (
+	"MeterService/controller/dto"
+	"MeterService/core/api"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+func AddDevice(c *gin.Context) {
+	device := &dto.Device{}
+	if err := c.BindJSON(device); err != nil {
+		api.Fail(c, err.Error())
+	}
+	if err := device.AddOrUpdate(); err != nil {
+		api.Fail(c, err.Error())
+	}
+	api.Ok(c)
+}
+
+func UpdateDevice(c *gin.Context) {
+	device := &dto.Device{}
+	if err := c.BindJSON(device); err != nil {
+		api.Fail(c, err.Error())
+	}
+	if err := device.Delete(); err != nil {
+		api.Fail(c, err.Error())
+	}
+	if err := device.AddOrUpdate(); err != nil {
+		api.Fail(c, err.Error())
+	}
+	api.Ok(c)
+}
+
+func DeleteDevice(c *gin.Context) {
+	device := &dto.Device{}
+	if err := c.BindJSON(device); err != nil {
+		api.Fail(c, err.Error())
+	}
+	if err := device.Delete(); err != nil {
+		api.Fail(c, err.Error())
+	}
+	api.Ok(c)
+}
+
+func Mock(c *gin.Context) {
+	var data []dto.Device
+	device := dto.Device{
+		Enable: true,
+		SN:     "202402200322",
+		//IP:       "www.shvber.com",
+		IP:       "192.168.0.104",
+		Port:     7777,
+		Protocol: "YC-HJ212",
+		Pw:       "123456",
+		Secs:     5,
+		St:       "01",
+		Cn:       "4",
+		Others:   "",
+		Mn:       "123",
+		Name:     "MOCK_METER",
+		ID:       1,
+		SimId:    "",
+		Slave: []dto.DeviceSlave{
+			{
+				Addr:  1,
+				NO:    "2024031185256281581",
+				MType: "ADW300",
+				LvRef: 220,
+				PvRef: 380,
+			},
+		},
+	}
+	data = append(data, device)
+	c.JSON(http.StatusOK, data)
+}

+ 39 - 0
SERVER/Meter_Service/controller/dto/app.go

@@ -0,0 +1,39 @@
+package dto
+
+import "MeterService/database/appApi"
+
+type App struct {
+	AppId     int       `json:"appId"`
+	AppSecret string    `json:"appSecret"`
+	AppName   string    `json:"appName"`
+	Host      string    `json:"host"`
+	Apis      []*AppApi `json:"apis"`
+}
+
+type AppApi struct {
+	Url  string `json:"url"`
+	Type string `json:"type"`
+}
+
+type RespApp struct {
+	AppId     int    `json:"appId"`
+	AppSecret string `json:"appSecret"`
+}
+
+func (m *App) ToDbApp() *appApi.App {
+	dbApp := appApi.NewApp()
+	dbApp.AppName = m.AppName
+	dbApp.AppSecret = m.AppSecret
+	dbApp.AppId = m.AppId
+	dbApp.Host = m.Host
+	dbApp.Apis = make([]*appApi.AppApi, 0)
+	for _, v := range m.Apis {
+		dbApp.Apis = append(dbApp.Apis, &appApi.AppApi{
+			AppId: m.AppId,
+			Type:  v.Type,
+			Host:  m.Host,
+			Url:   v.Url,
+		})
+	}
+	return dbApp
+}

+ 87 - 0
SERVER/Meter_Service/controller/dto/device.go

@@ -0,0 +1,87 @@
+package dto
+
+import (
+	"MeterService/data"
+	"MeterService/dataStruct"
+	"encoding/json"
+)
+
+type Device struct {
+	ID       int           `json:"id"`       // 设备ID
+	Enable   bool          `json:"enable"`   // 是否启用
+	SN       string        `json:"sn"`       // 设备编码SN
+	Name     string        `json:"name"`     // 设备名称
+	SimId    string        `json:"simId"`    // sim卡ID
+	IP       string        `json:"ip"`       // 平台IP
+	Port     int           `json:"port"`     // 平台端口
+	Protocol string        `json:"protocol"` // 平台协议
+	Pw       string        `json:"pw"`       // 平台密码
+	Mn       string        `json:"mn"`       // 设备MN
+	Secs     int           `json:"secs"`     // 上报周期
+	St       string        `json:"st"`       // 设备ST
+	Cn       string        `json:"cn"`       // 设备CN
+	Others   string        `json:"others"`   // 其他
+	Slave    []DeviceSlave `json:"slave"`    // 从机
+}
+
+type DeviceSlave struct {
+	Addr  int               `json:"addr"`  //设备串口地址
+	NO    string            `json:"no"`    //设备编号
+	LvRef float32           `json:"lvRef"` //线电压基准(220)
+	PvRef float32           `json:"pvRef"` //相电压基准(380)
+	MType string            `json:"mType"` //设备类型
+	BmYz  map[string]string `json:"bmYz"`  //编码因子
+}
+
+// AddOrUpdate 添加或更新设备
+func (d *Device) AddOrUpdate() error {
+	var dtuSlave []*dataStruct.DtuSlave
+	for _, slave := range d.Slave {
+		dtuSlave = append(dtuSlave, &dataStruct.DtuSlave{
+			Addr:  slave.Addr,
+			BmYz:  slave.BmYz,
+			LvRef: slave.LvRef,
+			MType: slave.MType,
+			PvRef: slave.PvRef,
+			NO:    slave.NO,
+		})
+	}
+	dtuConfig := &dataStruct.DtuConfig{
+		Enable:   d.Enable,
+		ID:       d.ID,
+		Name:     d.Name,
+		IP:       d.IP,
+		Secs:     d.Secs,
+		St:       d.St,
+		Cn:       d.Cn,
+		Others:   d.Others,
+		Port:     d.Port,
+		Protocol: d.Protocol,
+		Pw:       d.Pw,
+		Mn:       d.Mn,
+		Slave:    dtuSlave,
+	}
+	dtuConfigStr, err := json.Marshal(dtuConfig)
+	if err != nil {
+		return err
+	}
+	dtuDevice := &dataStruct.DTUDevice{
+		Configs: string(dtuConfigStr),
+		Name:    d.Name,
+		SimId:   d.SimId,
+		SN:      d.SN,
+	}
+	deviceState := &dataStruct.DTUDeviceState{
+		Info:   dtuDevice,
+		Online: false,
+		PwrOff: false,
+	}
+	data.DtuMap.Add(d.SN, deviceState)
+	return err
+}
+
+// Delete 删除设备
+func (d *Device) Delete() error {
+	data.DtuMap.Remove(d.SN)
+	return nil
+}

+ 11 - 0
SERVER/Meter_Service/controller/index.go

@@ -0,0 +1,11 @@
+package controller
+
+import (
+	"MeterService/core/api"
+
+	"github.com/gin-gonic/gin"
+)
+
+func Index(c *gin.Context) {
+	api.Ok2(c, "Web启动成功")
+}

+ 33 - 0
SERVER/Meter_Service/core/api/api.go

@@ -0,0 +1,33 @@
+package api
+
+import (
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+type Response struct {
+	Code    int         `json:"code"`
+	Message string      `json:"message"`
+	Data    interface{} `json:"data"`
+}
+
+func Ok(c *gin.Context) {
+	Ok2(c, nil)
+}
+func Ok2(c *gin.Context, data interface{}) {
+	AjaxResponse(c, 200, "success", data)
+}
+func Fail(c *gin.Context, message string) {
+	AjaxResponse(c, 500, message, nil)
+}
+func FailByCode(c *gin.Context, code int, message string) {
+	AjaxResponse(c, code, message, nil)
+}
+
+func AjaxResponse(c *gin.Context, code int, message string, data interface{}) {
+	c.JSON(http.StatusOK, Response{
+		Code:    code,
+		Message: message,
+		Data:    data})
+}

+ 68 - 0
SERVER/Meter_Service/core/config/config.go

@@ -0,0 +1,68 @@
+package config
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+
+	"github.com/fsnotify/fsnotify"
+	"github.com/spf13/pflag"
+	"github.com/spf13/viper"
+)
+
+type Configs struct {
+	Vber   vberConfig   `yaml:"vber"`
+	Redis  RedisConfig  `yaml:"redis"`
+	Mysql  MysqlConfig  `yaml:"mysql"`
+	Logger LoggerConfig `yaml:"log"`
+}
+
+var (
+	//获取命令行参数
+	conf = pflag.StringP("config", "c", "", "config filepath")
+	C    *Configs
+)
+
+func init() {
+	pflag.Parse()
+	if err := initConfig(*conf); err != nil {
+		log.Println("Config配置失败:", err.Error())
+		panic("Config配置失败")
+	}
+	viper.WatchConfig()
+}
+
+func initConfig(name string) error {
+	if name != "" {
+		viper.SetConfigFile(name)
+	} else {
+		// 默认配置文件路径 ./config.yaml
+		viper.AddConfigPath("./")
+		viper.SetConfigName("config")
+	}
+	viper.SetConfigType("yaml")
+
+	// 读取配置文件
+	err := viper.ReadInConfig()
+	if err != nil {
+		panic(fmt.Errorf("Fatal error config file: %s \n", err))
+	}
+
+	if err = viper.Unmarshal(&C); err != nil {
+		return err
+	}
+	// 打印下配置
+	marshal, err := json.Marshal(C)
+	if err == nil {
+		fmt.Println("加载配置文件:", string(marshal))
+	}
+
+	return nil
+}
+
+func (c *Configs) watch() {
+	viper.WatchConfig()
+	viper.OnConfigChange(func(e fsnotify.Event) {
+		fmt.Println("C file changed:", e.Name)
+	})
+}

+ 10 - 0
SERVER/Meter_Service/core/config/loggerConfig.go

@@ -0,0 +1,10 @@
+package config
+
+type LoggerConfig struct {
+	FilePath    string `yaml:"filePath"`
+	FileType    string `yaml:"fileType"`
+	FileFormat  string `yaml:"fileFormat"`
+	LogType     string `yaml:"logType"`
+	Level       string `yaml:"level"`
+	MaxSaveDays int    `yaml:"maxSaveDays"`
+}

+ 12 - 0
SERVER/Meter_Service/core/config/mysqlConfig.go

@@ -0,0 +1,12 @@
+package config
+
+type MysqlConfig struct {
+	Host         string `yaml:"host"`
+	Port         int    `yaml:"port"`
+	Username     string `yaml:"username"`
+	Password     string `yaml:"password"`
+	Name         string `yaml:"name"`
+	MaxIdleConns int    `yaml:"maxIdleConns"`
+	MaxOpenConns int    `yaml:"maxOpenConns"`
+	Log          bool   `yaml:"log"`
+}

+ 8 - 0
SERVER/Meter_Service/core/config/redisConfig.go

@@ -0,0 +1,8 @@
+package config
+
+type RedisConfig struct {
+	Host     string `yaml:"host"`
+	Port     int    `yaml:"port"`
+	Password string `yaml:"password"`
+	Db       int    `yaml:"db"`
+}

+ 11 - 0
SERVER/Meter_Service/core/config/vberConfig.go

@@ -0,0 +1,11 @@
+package config
+
+type vberConfig struct {
+	Name        string `yaml:"name"`
+	Description string `yaml:"description"`
+	Version     string `yaml:"version"`
+	WebPort     int    `yaml:"webPort"`
+	CmdPort     int    `yaml:"cmdPort"`
+	FwdPort     int    `yaml:"fwdPort"`
+	Mode        string `yaml:"mode"`
+}

+ 102 - 0
SERVER/Meter_Service/core/constant/constant.go

@@ -0,0 +1,102 @@
+package constant
+
+const (
+	Version              = "1.3.0"
+	TYPE_REQUST          = "request"
+	TYPE_RESPONSE        = "response"
+	CMD_GETTHRESCFG      = "gethrescfg"
+	CMD_SETTHRESCFG      = "sethrescfg"
+	CMD_GETCFGCFG        = "getconfigcfg"
+	CMD_SETCFGCFG        = "setconfigcfg"
+	CMD_ADDCFGCFG        = "addconfigcfg"
+	CMD_GETSYSCFG        = "getsystemcfg"
+	CMD_SETSYSCFG        = "setsystemcfg"
+	CMD_REBOOT           = "reboot"
+	CMD_RERUN            = "rerun"
+	CMD_GETSTATE         = "getstate"
+	CMD_GETVERSION       = "getversion"
+	CMD_GETXTFILE        = "getxtfile"
+	CMD_SETXTFILE        = "setxtfile"
+	CMD_LOGIN            = "login"
+	CMD_KEEPALIVE        = "keepalive"
+	CMD_STARTFRP         = "startfrpc"
+	CMD_GETFILEMD5       = "getmd5file"
+	CMD_FILEUPLOAD       = "fileupload"
+	CMD_FILEDOWNLOAD     = "filedownload"
+	CMD_POWERONOFF       = "poweronoff"
+	CMD_PURIFIERSTATE    = "setpurifierst"
+	CMD_PURIFIERSTATEDO2 = "setpurifido2"
+	CMD_TMRPLANPURI      = "puritmrplan"
+	CMD_TMRPLANATOM      = "tmrplanatom"
+	CmdUpgradeFile       = "upgrade"
+	Cmdforward           = "forward"
+	CMD_GETDTUBASEINFO   = "getdtubaseinfo"
+	CMD_SETLRFREQ        = "setlrfreq"
+	PurifiDOClose        = true  //净化器/雾化器关状态
+	PurifiDOOpen         = false //净化器/雾化器开状态
+
+	CODE_GETTHRESCFG    = 0x01
+	CODE_SETTHRESCFG    = 0x02
+	CODE_GETCFGCFG      = 0x03
+	CODE_SETCFGCFG      = 0x04
+	CODE_GETSYSCFG      = 0x05
+	CODE_SETSYSCFG      = 0x06
+	CODE_REBOOT         = 0x07
+	CODE_RERUN          = 0x08
+	CODE_GETSTATE       = 0x09
+	CODE_GETVERSION     = 10
+	CODE_GETXTFILE      = 11
+	CODE_SETXTFILE      = 12
+	CODE_LOGIN          = 15
+	CODE_KEEPALIVE      = 16
+	CODE_STARTFRP       = 18
+	CODE_GETFILEMD5     = 19
+	CODE_FILEUPLOAD     = 20
+	CODE_FILEDOWNLOAD   = 21
+	CODE_POWERONOFF     = 22
+	CODE_PURIFIERSTATE  = 23
+	CODE_UpgradeFile    = 24
+	CODE_GETDTUBASEINFO = 25
+
+	LoginErrUsr          = 1000 //用户名密码错误
+	LoginErrTokenMiss    = 1001 //没有token
+	LoginErrIncomplete   = 1002 //token不完整Invalid
+	LoginErrTokenInvalid = 1003 //token失效
+	LoginUsrLMT          = 1004 //账号权限不足
+
+	LoginErrUsrExsit = 1009 //账号已经存在
+	LoginErrUndefine = 2000
+
+	LenMN        = 32 //MN-char length
+	LenBYZM      = 20 //BYZM-char length
+	LenIP        = 16 //IP-char length
+	LenPW        = 8  //PW-char length
+	LenProtoDev  = 16 //设备协议长度
+	LenProtoPlat = 16 //设备协议长度
+	LenName      = 64 //name length
+	MaxPlatNum   = 10 //平台数量最大值
+
+	HTC_OK          = 0    //通信机
+	HTC_DTUNoExist  = 2000 //通信机不存在
+	HTC_DTUOffLine  = 2001 //通信机离线
+	HTC_DTUOverTime = 2002 //通信机超时
+	HTC_DTUErr      = 2003 //通信机响应错误
+
+	HTC_HtmlParam   = 2010 //网页传入参数错误
+	HTC_MeterAddr   = 2011 //电表地址错误
+	HTC_LoRaFreq    = 2012 //LoRa频率错误
+	HTC_JSONFormat  = 2013 //json格式错误
+	HTC_NewCompany  = 2020 //新增公司信息失败
+	HTC_EditCompany = 2021 //修改公司信息失败
+	HTC_OptDB       = 2022 //数据库操作错误
+	HTC_UnCPR       = 2030 //解压错误
+	HTC_SNLENGTH    = 2031 //SN长度错误
+
+	HTC_Undefine    = 9999 //未定义错误
+	TCPTimeoutTime  = 450  //tcp超时时间,秒
+	HeadFlagBinPack = 0xA5
+	LengthSNAct     = 12
+	PermIDRoot      = 1
+	PermIDAgent     = 2
+	PermIDVisiter   = 3
+)

+ 103 - 0
SERVER/Meter_Service/core/db/db.go

@@ -0,0 +1,103 @@
+package db
+
+import (
+	"MeterService/core/utils"
+	"database/sql"
+	"sync"
+)
+
+var (
+	err  error
+	myDb *MyDB
+)
+
+var once sync.Once
+
+type MyDB struct {
+	DB *sql.DB
+}
+
+// OpenDb 获取数据库连接
+func OpenDb(dbFun func() *sql.DB) {
+	once.Do(func() {
+		myDb = &MyDB{DB: dbFun()}
+	})
+}
+func GetDb() *MyDB {
+	return myDb
+}
+func CloseDb() {
+	err := myDb.DB.Close()
+	if err != nil {
+		return
+	} else {
+		panic(err)
+	}
+}
+
+// Exec 增、删、改
+func (db *MyDB) Exec(SQL string, args ...interface{}) (sql.Result, error) {
+	//DB := OpenDb().DB()
+	var ret sql.Result
+	if args == nil {
+		ret, err = db.DB.Exec(SQL)
+	} else {
+		ret, err = db.DB.Exec(SQL, args...)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return ret, nil
+}
+
+// Query 查询
+func (db *MyDB) Query(SQL string, args ...interface{}) ([]map[string]string, bool) { //通用查询
+	var rows *sql.Rows
+	if args == nil {
+		rows, err = db.DB.Query(SQL)
+	} else {
+		rows, err = db.DB.Query(SQL, args...)
+	}
+	//rows, err := DB.Query(SQL, args) //执行SQL语句,比如select * from users
+	if err != nil {
+		panic(err)
+	}
+	columns, _ := rows.Columns()            //获取列的信息
+	count := len(columns)                   //列的数量
+	var values = make([]interface{}, count) //创建一个与列的数量相当的空接口
+	for i, _ := range values {
+		var ii interface{} //为空接口分配内存
+		values[i] = &ii    //取得这些内存的指针,因后继的Scan函数只接受指针
+	}
+	ret := make([]map[string]string, 0) //创建返回值:不定长的map类型切片
+	for rows.Next() {
+		err := rows.Scan(values...)  //开始读行,Scan函数只接受指针变量
+		m := make(map[string]string) //用于存放1列的 [键/值] 对
+		if err != nil {
+			panic(err)
+		}
+		for i, colName := range columns {
+			var rawValue = *(values[i].(*interface{})) //读出raw数据,类型为byte
+			//b, _ := rawValue.([]byte)
+			//v := string(b) //将raw数据转换成字符串
+			m[colName], err = utils.ToString(rawValue) //colName是键,v是值
+			if err != nil {
+				panic(err)
+			}
+		}
+		ret = append(ret, m) //将单行所有列的键值对附加在总的返回值上(以行为单位)
+	}
+
+	defer func(rows *sql.Rows) {
+		_ = rows.Close()
+	}(rows)
+
+	if len(ret) != 0 {
+		return ret, true
+	}
+	return nil, false
+}
+
+func NewDB(db *sql.DB) *MyDB {
+	return &MyDB{DB: db}
+}

+ 122 - 0
SERVER/Meter_Service/core/db/mysql/mysql.go

@@ -0,0 +1,122 @@
+package mysql
+
+import (
+	"MeterService/core/config"
+	"database/sql"
+	"errors"
+	"fmt"
+	"sync"
+
+	orm "github.com/jinzhu/gorm"
+	_ "github.com/jinzhu/gorm/dialects/mysql"
+)
+
+type MySqlPool struct{}
+
+var once sync.Once
+var err error
+var db *orm.DB
+
+func GetDb() *orm.DB {
+	once.Do(func() {
+		db = openMySqlPool()
+	})
+	return db
+}
+
+func openMySqlPool() *orm.DB {
+	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+		config.C.Mysql.Username, config.C.Mysql.Password,
+		config.C.Mysql.Host, config.C.Mysql.Port, config.C.Mysql.Name)
+	db, err := orm.Open("mysql", dsn)
+	if err != nil {
+		panic(errors.New("mysql连接失败。" + err.Error()))
+	}
+	if config.C.Mysql.MaxIdleConns > 0 {
+		db.DB().SetMaxIdleConns(config.C.Mysql.MaxIdleConns)
+	}
+	if config.C.Mysql.MaxOpenConns > 0 {
+		db.DB().SetMaxOpenConns(config.C.Mysql.MaxOpenConns)
+	}
+	if config.C.Mysql.Log {
+		db.LogMode(config.C.Mysql.Log)
+	}
+	return db
+}
+
+func CloseDb(db *orm.DB) {
+	err := db.Close()
+	if err != nil {
+		return
+	} else {
+		panic(err)
+	}
+}
+
+func (pool *MySqlPool) InitPool() (isSuc bool) {
+	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+		config.C.Mysql.Username, config.C.Mysql.Password,
+		config.C.Mysql.Host, config.C.Mysql.Port, config.C.Mysql.Name)
+	db, err := orm.Open("mysql", dsn)
+	if err != nil {
+		panic(err)
+		return false
+	}
+	// 连接数配置也可以写入配置,在此读取
+	if config.C.Mysql.MaxIdleConns > 0 {
+		db.DB().SetMaxIdleConns(config.C.Mysql.MaxIdleConns)
+	}
+	if config.C.Mysql.MaxOpenConns > 0 {
+		db.DB().SetMaxOpenConns(config.C.Mysql.MaxOpenConns)
+	}
+	return true
+}
+
+// Exec 增、删、改
+func Exec(SQL string, args ...interface{}) (sql.Result, error) {
+	DB := GetDb().DB()
+	ret, err := DB.Exec(SQL, args)
+	if err != nil {
+		return nil, err
+	}
+	return ret, nil
+}
+
+func Query(SQL string, args ...interface{}) ([]map[string]string, bool) { //通用查询
+	DB := GetDb().DB()
+	rows, err := DB.Query(SQL, args) //执行SQL语句,比如select * from users
+	if err != nil {
+		panic(err)
+	}
+	columns, _ := rows.Columns()            //获取列的信息
+	count := len(columns)                   //列的数量
+	var values = make([]interface{}, count) //创建一个与列的数量相当的空接口
+	for i, _ := range values {
+		var ii interface{} //为空接口分配内存
+		values[i] = &ii    //取得这些内存的指针,因后继的Scan函数只接受指针
+	}
+	ret := make([]map[string]string, 0) //创建返回值:不定长的map类型切片
+	for rows.Next() {
+		err := rows.Scan(values...)  //开始读行,Scan函数只接受指针变量
+		m := make(map[string]string) //用于存放1列的 [键/值] 对
+		if err != nil {
+			panic(err)
+		}
+		for i, colName := range columns {
+			var rawValue = *(values[i].(*interface{})) //读出raw数据,类型为byte
+			b, _ := rawValue.([]byte)
+			v := string(b) //将raw数据转换成字符串
+			m[colName] = v //colName是键,v是值
+		}
+		ret = append(ret, m) //将单行所有列的键值对附加在总的返回值上(以行为单位)
+	}
+
+	defer func(rows *sql.Rows) {
+		_ = rows.Close()
+	}(rows)
+
+	if len(ret) != 0 {
+		return ret, true
+	}
+	return nil, false
+}

+ 23 - 0
SERVER/Meter_Service/core/db/redis.go

@@ -0,0 +1,23 @@
+package db
+
+import (
+	"MeterService/core/config"
+	"fmt"
+
+	"github.com/go-redis/redis"
+)
+
+var RedisClient *redis.Client
+
+func InitRedis() {
+	RedisClient = redis.NewClient(&redis.Options{
+		Addr:     fmt.Sprintf("%s:%d", config.C.Redis.Host, config.C.Redis.Port),
+		Password: config.C.Redis.Password,
+		DB:       config.C.Redis.Db,
+	})
+	_, err := RedisClient.Ping().Result()
+	if err != nil {
+		fmt.Println("redis connect ping failed, err:", err)
+	}
+	return
+}

+ 51 - 0
SERVER/Meter_Service/core/db/sqlite/sqlite.go

@@ -0,0 +1,51 @@
+package sqlite
+
+import (
+	"database/sql"
+	"sync"
+
+	orm "github.com/jinzhu/gorm"
+	_ "github.com/jinzhu/gorm/dialects/sqlite"
+)
+
+var once sync.Once
+var err error
+var db *orm.DB
+
+func GetDb() *orm.DB {
+	once.Do(func() {
+		db = openPool()
+	})
+	return db
+}
+
+func CloseDb() {
+	err := db.Close()
+	if err != nil {
+		return
+	} else {
+		panic(err)
+	}
+}
+
+func openPool() *orm.DB {
+	db, err := orm.Open("sqlite3", "./database/vber.db")
+	if err != nil {
+		panic(err)
+	}
+	//db.SingularTable(true)
+	db.DB().SetMaxIdleConns(20)
+	db.DB().SetMaxOpenConns(20)
+	return db
+}
+
+func OpenSQLite() *sql.DB {
+	sqliteDb, err := orm.Open("sqlite3", "./database/vber.db")
+	if err != nil {
+		panic(err)
+	}
+	db := sqliteDb.DB()
+	db.SetMaxIdleConns(20)
+	db.SetMaxOpenConns(20)
+	return db
+}

+ 132 - 0
SERVER/Meter_Service/core/log.go

@@ -0,0 +1,132 @@
+package logger2
+
+import (
+	cfg "MeterService/core/config"
+	"io"
+	"log"
+	"time"
+
+	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+var Logger *zap.Logger
+
+func InitLogger() {
+	config := zapcore.EncoderConfig{
+		TimeKey:    "ts",
+		LevelKey:   "level",
+		MessageKey: "msg",
+		//CallerKey:   "file",
+		EncodeLevel: zapcore.CapitalLevelEncoder,
+		EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+			enc.AppendString(t.Format("2006-01-02 15:04:05"))
+		},
+		EncodeDuration: func(duration time.Duration, encoder zapcore.PrimitiveArrayEncoder) {
+			encoder.AppendInt64(int64(duration) / 100000000) // nano -> milli
+		},
+		EncodeCaller: zapcore.ShortCallerEncoder,
+	}
+	encoder := zapcore.NewConsoleEncoder(config)
+	if cfg.C.Logger.LogType == "json" {
+		encoder = zapcore.NewJSONEncoder(config)
+	}
+	fileFormat, saveType, logLevelStr := "%Y%m%d", "one", "info"
+	if cfg.C.Logger.FileFormat != "" {
+		fileFormat = cfg.C.Logger.FileFormat
+	}
+	if cfg.C.Logger.Level != "" {
+		logLevelStr = cfg.C.Logger.Level
+	}
+	if cfg.C.Logger.FileType != "" {
+		saveType = cfg.C.Logger.FileType
+	}
+	logLevel := zap.DebugLevel
+	switch logLevelStr {
+	case "debug":
+		logLevel = zap.DebugLevel
+	case "info":
+		logLevel = zap.InfoLevel
+	case "error":
+		logLevel = zap.ErrorLevel
+	}
+
+	switch saveType {
+	case "level":
+		Logger = getLevelLogger(encoder, logLevel, fileFormat)
+	default:
+		Logger = getOneLogger(encoder, logLevel, fileFormat)
+	}
+}
+
+func getLevelLogger(encoder zapcore.Encoder, logLevel zapcore.Level, fileFormat string) *zap.Logger {
+	debugLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
+		return lvl >= logLevel && lvl == zap.DebugLevel
+	})
+	infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
+		return lvl >= logLevel && lvl == zap.InfoLevel
+	})
+	errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
+		return lvl >= logLevel && lvl == zap.ErrorLevel
+	})
+
+	core := zapcore.NewTee(
+		zapcore.NewCore(encoder, zapcore.AddSync(getLoggerWriter("debug", fileFormat)), debugLevel),
+		zapcore.NewCore(encoder, zapcore.AddSync(getLoggerWriter("info", fileFormat)), infoLevel),
+		zapcore.NewCore(encoder, zapcore.AddSync(getLoggerWriter("error", fileFormat)), errorLevel),
+	)
+	return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.WarnLevel))
+}
+
+func getOneLogger(encoder zapcore.Encoder, logLevel zapcore.Level, fileFormat string) *zap.Logger {
+	core := zapcore.NewCore(encoder, zapcore.AddSync(getLoggerWriter("all", fileFormat)), logLevel)
+	return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.WarnLevel))
+}
+
+func getLoggerWriter(filename, fileFormat string) io.Writer {
+	filePath, saveDay := "./logs", 30
+	if cfg.C.Logger.FilePath != "" {
+		filePath = cfg.C.Logger.FilePath
+	}
+	if cfg.C.Logger.MaxSaveDays > 0 {
+		saveDay = cfg.C.Logger.MaxSaveDays
+	}
+	filename = filePath + "/" + filename
+	hook, err := rotatelogs.New(filename+"/"+fileFormat+".log",
+		rotatelogs.WithLinkName(filename),
+		rotatelogs.WithMaxAge(time.Duration(saveDay)*24*time.Hour),
+		rotatelogs.WithRotationTime(24*time.Hour),
+	)
+	if err != nil {
+		log.Println("日志启动异常")
+		panic(err)
+	}
+	return hook
+}
+
+func Debug(format string, v ...interface{}) {
+	if cfg.C.Vber.Mode == "debug" {
+		if v == nil {
+			Logger.Sugar().Debug(format)
+		} else {
+			Logger.Sugar().Debugf(format, v...)
+		}
+	}
+}
+
+func Info(format string, v ...interface{}) {
+	if v == nil {
+		Logger.Sugar().Info(format)
+	} else {
+		Logger.Sugar().Infof(format, v...)
+	}
+}
+
+func Error(format string, v ...interface{}) {
+	if v == nil {
+		Logger.Sugar().Error(format)
+	} else {
+		Logger.Sugar().Errorf(format, v...)
+	}
+}

+ 148 - 0
SERVER/Meter_Service/core/logger/logger.go

@@ -0,0 +1,148 @@
+package logger
+
+import (
+	cfg "MeterService/core/config"
+	"errors"
+	"io"
+	"log"
+	"sync"
+	"time"
+
+	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+var (
+	Logger   *zap.Logger
+	loggerMu sync.Mutex
+)
+
+func init() {
+	err := InitLogger()
+	if err != nil {
+		log.Println("logger初始化失败")
+		panic(err)
+	}
+}
+func InitLogger() error {
+	loggerMu.Lock()
+	defer loggerMu.Unlock()
+	config := getConfig()
+	encoder := createEncoder(config.LogType)
+	logLevel := getLogLevel(config.Level)
+
+	var cores []zapcore.Core
+	switch config.FileType {
+	case "level":
+		debugCore := zapcore.NewCore(encoder, zapcore.AddSync(getLoggerWriter("debug", config.FileFormat)), zap.DebugLevel)
+		infoCore := zapcore.NewCore(encoder, zapcore.AddSync(getLoggerWriter("info", config.FileFormat)), zap.InfoLevel)
+		errorCore := zapcore.NewCore(encoder, zapcore.AddSync(getLoggerWriter("error", config.FileFormat)), zap.ErrorLevel)
+		cores = append(cores, debugCore, infoCore, errorCore)
+	default:
+		core := zapcore.NewCore(encoder, zapcore.AddSync(getLoggerWriter("all", config.FileFormat)), logLevel)
+		cores = append(cores, core)
+	}
+
+	core := zapcore.NewTee(cores...)
+	newLogger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.WarnLevel))
+
+	if newLogger == nil {
+		return errors.New("failed to initialize logger")
+	}
+
+	Logger = newLogger
+	return nil
+}
+
+func getConfig() *cfg.LoggerConfig {
+	return &cfg.C.Logger
+}
+
+func createEncoder(logType string) zapcore.Encoder {
+	config := zapcore.EncoderConfig{
+		TimeKey:        "ts",
+		LevelKey:       "level",
+		MessageKey:     "msg",
+		EncodeTime:     customEncodeTime,
+		EncodeDuration: customEncodeDuration,
+		EncodeCaller:   zapcore.ShortCallerEncoder,
+	}
+
+	if logType == "json" {
+		return zapcore.NewJSONEncoder(config)
+	}
+	return zapcore.NewConsoleEncoder(config)
+}
+
+func getLogLevel(level string) zapcore.Level {
+	var logLevel zapcore.Level
+	switch level {
+	case "debug":
+		logLevel = zap.DebugLevel
+	case "info":
+		logLevel = zap.InfoLevel
+	case "error":
+		logLevel = zap.ErrorLevel
+	default:
+		logLevel = zap.InfoLevel // 默认日志级别
+	}
+	return logLevel
+}
+
+func getLoggerWriter(filename, fileFormat string) io.Writer {
+	filePath, saveDay := "./logs", 30
+	if cfg.C.Logger.FilePath != "" {
+		filePath = cfg.C.Logger.FilePath
+	}
+	if cfg.C.Logger.MaxSaveDays > 0 {
+		saveDay = cfg.C.Logger.MaxSaveDays
+	}
+	filename = filePath + "/" + filename
+	// 日志切割
+	hook, err := rotatelogs.New(
+		filename+"/"+fileFormat+".log",
+		//rotatelogs.WithLinkName(filename),
+		rotatelogs.WithMaxAge(time.Duration(saveDay)*24*time.Hour),
+		rotatelogs.WithRotationTime(24*time.Hour),
+	)
+	if err != nil {
+		log.Printf("日志启动异常: %v\n", err)
+		return nil
+	}
+	return hook
+}
+
+func customEncodeTime(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+	enc.AppendString(t.Format("2006-01-02 15:04:05"))
+}
+
+func customEncodeDuration(duration time.Duration, encoder zapcore.PrimitiveArrayEncoder) {
+	encoder.AppendInt64(int64(duration) / 100000000) // nano -> milli
+}
+
+func Debug(format string, v ...interface{}) {
+	if cfg.C.Vber.Mode == "debug" {
+		if v == nil {
+			Logger.Sugar().Debug(format)
+		} else {
+			Logger.Sugar().Debugf(format, v...)
+		}
+	}
+}
+
+func Info(format string, v ...interface{}) {
+	if v == nil {
+		Logger.Sugar().Info(format)
+	} else {
+		Logger.Sugar().Infof(format, v...)
+	}
+}
+
+func Error(format string, v ...interface{}) {
+	if v == nil {
+		Logger.Sugar().Error(format)
+	} else {
+		Logger.Sugar().Errorf(format, v...)
+	}
+}

+ 10 - 0
SERVER/Meter_Service/core/modbus/.travis.yml

@@ -0,0 +1,10 @@
+language: go
+
+go:
+  - 1.2
+  - 1.3
+  - 1.4
+  - tip
+
+script:
+  - go test -v -bench . -benchmem

+ 26 - 0
SERVER/Meter_Service/core/modbus/LICENSE

@@ -0,0 +1,26 @@
+Copyright (C) 2014 Quoc-Viet Nguyen
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation  and/or other materials provided with the distribution.
+3. Neither the names of the copyright holders nor the names of any
+   contributors may be used to endorse or promote products derived from this
+   software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.

+ 78 - 0
SERVER/Meter_Service/core/modbus/README.md

@@ -0,0 +1,78 @@
+go modbus [![Build Status](https://travis-ci.org/goburrow/modbus.svg?branch=master)](https://travis-ci.org/goburrow/modbus) [![GoDoc](https://godoc.org/github.com/goburrow/modbus?status.svg)](https://godoc.org/github.com/goburrow/modbus)
+=========
+Fault-tolerant, fail-fast implementation of Modbus protocol in Go.
+
+Supported functions
+-------------------
+Bit access:
+*   Read Discrete Inputs
+*   Read Coils
+*   Write Single Coil
+*   Write Multiple Coils
+
+16-bit access:
+*   Read Input Registers
+*   Read Holding Registers
+*   Write Single Register
+*   Write Multiple Registers
+*   Read/Write Multiple Registers
+*   Mask Write Register
+*   Read FIFO Queue
+
+Supported formats
+-----------------
+*   TCP
+*   Serial (RTU, ASCII)
+
+Usage
+-----
+Basic usage:
+```go
+// Modbus TCP
+client := modbus.TCPClient("localhost:502")
+// Read input register 9
+results, err := client.ReadInputRegisters(8, 1)
+
+// Modbus RTU/ASCII
+// Default configuration is 19200, 8, 1, even
+client = modbus.RTUClient("/dev/ttyS0")
+results, err = client.ReadCoils(2, 1)
+```
+
+Advanced usage:
+```go
+// Modbus TCP
+handler := modbus.NewTCPClientHandler("localhost:502")
+handler.Timeout = 10 * time.Second
+handler.SlaveId = 0xFF
+handler.Logger = log.New(os.Stdout, "test: ", log.LstdFlags)
+// Connect manually so that multiple requests are handled in one connection session
+err := handler.Connect()
+defer handler.Close()
+
+client := modbus.NewClient(handler)
+results, err := client.ReadDiscreteInputs(15, 2)
+results, err = client.WriteMultipleRegisters(1, 2, []byte{0, 3, 0, 4})
+results, err = client.WriteMultipleCoils(5, 10, []byte{4, 3})
+```
+
+```go
+// Modbus RTU/ASCII
+handler := modbus.NewRTUClientHandler("/dev/ttyUSB0")
+handler.BaudRate = 115200
+handler.DataBits = 8
+handler.Parity = "N"
+handler.StopBits = 1
+handler.SlaveId = 1
+handler.Timeout = 5 * time.Second
+
+err := handler.Connect()
+defer handler.Close()
+
+client := modbus.NewClient(handler)
+results, err := client.ReadDiscreteInputs(15, 2)
+```
+
+References
+----------
+-   [Modbus Specifications and Implementation Guides](http://www.modbus.org/specs.php)

+ 52 - 0
SERVER/Meter_Service/core/modbus/api.go

@@ -0,0 +1,52 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+type Client interface {
+	// Bit access
+
+	// ReadCoils reads from 1 to 2000 contiguous status of coils in a
+	// remote device and returns coil status.
+	ReadCoils(address, quantity uint16) (results []byte, err error)
+	// ReadDiscreteInputs reads from 1 to 2000 contiguous status of
+	// discrete inputs in a remote device and returns input status.
+	ReadDiscreteInputs(address, quantity uint16) (results []byte, err error)
+	// WriteSingleCoil write a single output to either ON or OFF in a
+	// remote device and returns output value.
+	WriteSingleCoil(address, value uint16) (results []byte, err error)
+	// WriteMultipleCoils forces each coil in a sequence of coils to either
+	// ON or OFF in a remote device and returns quantity of outputs.
+	WriteMultipleCoils(address, quantity uint16, value []byte) (results []byte, err error)
+
+	// 16-bit access
+
+	// ReadInputRegisters reads from 1 to 125 contiguous input registers in
+	// a remote device and returns input registers.
+	ReadInputRegisters(address, quantity uint16) (results []byte, err error)
+
+	PackReadHoldingRegisters(address, quantity uint16) (results []byte, err error)
+	DePackRegisters(aduResponse []byte) (results []byte, err error)
+	// ReadHoldingRegisters reads the contents of a contiguous block of
+	// holding registers in a remote device and returns register value.
+	ReadHoldingRegisters(address, quantity uint16) (results []byte, err error)
+	// WriteSingleRegister writes a single holding register in a remote
+	// device and returns register value.
+	WriteSingleRegister(address, value uint16) (results []byte, err error)
+	// WriteMultipleRegisters writes a block of contiguous registers
+	// (1 to 123 registers) in a remote device and returns quantity of
+	// registers.
+	WriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error)
+	// ReadWriteMultipleRegisters performs a combination of one read
+	// operation and one write operation. It returns read registers value.
+	ReadWriteMultipleRegisters(readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) (results []byte, err error)
+	// MaskWriteRegister modify the contents of a specified holding
+	// register using a combination of an AND mask, an OR mask, and the
+	// register's current contents. The function returns
+	// AND-mask and OR-mask.
+	MaskWriteRegister(address, andMask, orMask uint16) (results []byte, err error)
+	//ReadFIFOQueue reads the contents of a First-In-First-Out (FIFO) queue
+	// of register in a remote device and returns FIFO value register.
+	ReadFIFOQueue(address uint16) (results []byte, err error)
+}

+ 228 - 0
SERVER/Meter_Service/core/modbus/asciiclient.go

@@ -0,0 +1,228 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"time"
+)
+
+const (
+	asciiStart   = ":"
+	asciiEnd     = "\r\n"
+	asciiMinSize = 3
+	asciiMaxSize = 513
+
+	hexTable = "0123456789ABCDEF"
+)
+
+// ASCIIClientHandler implements Packager and Transporter interface.
+type ASCIIClientHandler struct {
+	asciiPackager
+	asciiSerialTransporter
+}
+
+// NewASCIIClientHandler allocates and initializes a ASCIIClientHandler.
+func NewASCIIClientHandler(address string) *ASCIIClientHandler {
+	handler := &ASCIIClientHandler{}
+	handler.Address = address
+	handler.Timeout = serialTimeout
+	handler.IdleTimeout = serialIdleTimeout
+	return handler
+}
+
+// ASCIIClient creates ASCII client with default handler and given connect string.
+func ASCIIClient(address string) Client {
+	handler := NewASCIIClientHandler(address)
+	return NewClient(handler)
+}
+
+// asciiPackager implements Packager interface.
+type asciiPackager struct {
+	SlaveId byte
+}
+
+// Encode encodes PDU in a ASCII frame:
+//
+//	Start           : 1 char
+//	Address         : 2 chars
+//	Function        : 2 chars
+//	Data            : 0 up to 2x252 chars
+//	LRC             : 2 chars
+//	End             : 2 chars
+func (mb *asciiPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) {
+	var buf bytes.Buffer
+
+	if _, err = buf.WriteString(asciiStart); err != nil {
+		return
+	}
+	if err = writeHex(&buf, []byte{mb.SlaveId, pdu.FunctionCode}); err != nil {
+		return
+	}
+	if err = writeHex(&buf, pdu.Data); err != nil {
+		return
+	}
+	// Exclude the beginning colon and terminating CRLF pair characters
+	var lrc lrc
+	lrc.reset()
+	lrc.pushByte(mb.SlaveId).pushByte(pdu.FunctionCode).pushBytes(pdu.Data)
+	if err = writeHex(&buf, []byte{lrc.value()}); err != nil {
+		return
+	}
+	if _, err = buf.WriteString(asciiEnd); err != nil {
+		return
+	}
+	adu = buf.Bytes()
+	return
+}
+
+// Verify verifies response length, frame boundary and slave id.
+func (mb *asciiPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) {
+	length := len(aduResponse)
+	// Minimum size (including address, function and LRC)
+	if length < asciiMinSize+6 {
+		err = fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", length, 9)
+		return
+	}
+	// Length excluding colon must be an even number
+	if length%2 != 1 {
+		err = fmt.Errorf("modbus: response length '%v' is not an even number", length-1)
+		return
+	}
+	// First char must be a colon
+	str := string(aduResponse[0:len(asciiStart)])
+	if str != asciiStart {
+		err = fmt.Errorf("modbus: response frame '%v'... is not started with '%v'", str, asciiStart)
+		return
+	}
+	// 2 last chars must be \r\n
+	str = string(aduResponse[len(aduResponse)-len(asciiEnd):])
+	if str != asciiEnd {
+		err = fmt.Errorf("modbus: response frame ...'%v' is not ended with '%v'", str, asciiEnd)
+		return
+	}
+	// Slave id
+	responseVal, err := readHex(aduResponse[1:])
+	if err != nil {
+		return
+	}
+	requestVal, err := readHex(aduRequest[1:])
+	if err != nil {
+		return
+	}
+	if responseVal != requestVal {
+		err = fmt.Errorf("modbus: response slave id '%v' does not match request '%v'", responseVal, requestVal)
+		return
+	}
+	return
+}
+
+// Decode extracts PDU from ASCII frame and verify LRC.
+func (mb *asciiPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) {
+	pdu = &ProtocolDataUnit{}
+	// Slave address
+	address, err := readHex(adu[1:])
+	if err != nil {
+		return
+	}
+	// Function code
+	if pdu.FunctionCode, err = readHex(adu[3:]); err != nil {
+		return
+	}
+	// Data
+	dataEnd := len(adu) - 4
+	data := adu[5:dataEnd]
+	pdu.Data = make([]byte, hex.DecodedLen(len(data)))
+	if _, err = hex.Decode(pdu.Data, data); err != nil {
+		return
+	}
+	// LRC
+	lrcVal, err := readHex(adu[dataEnd:])
+	if err != nil {
+		return
+	}
+	// Calculate checksum
+	var lrc lrc
+	lrc.reset()
+	lrc.pushByte(address).pushByte(pdu.FunctionCode).pushBytes(pdu.Data)
+	if lrcVal != lrc.value() {
+		err = fmt.Errorf("modbus: response lrc '%v' does not match expected '%v'", lrcVal, lrc.value())
+		return
+	}
+	return
+}
+
+// asciiSerialTransporter implements Transporter interface.
+type asciiSerialTransporter struct {
+	serialPort
+}
+
+func (mb *asciiSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) {
+	mb.serialPort.mu.Lock()
+	defer mb.serialPort.mu.Unlock()
+
+	// Make sure port is connected
+	if err = mb.serialPort.connect(); err != nil {
+		return
+	}
+	// Start the timer to close when idle
+	mb.serialPort.lastActivity = time.Now()
+	mb.serialPort.startCloseTimer()
+
+	// Send the request
+	mb.serialPort.logf("modbus: sending %q\n", aduRequest)
+	if _, err = mb.port.Write(aduRequest); err != nil {
+		return
+	}
+	// Get the response
+	var n int
+	var data [asciiMaxSize]byte
+	length := 0
+	for {
+		if n, err = mb.port.Read(data[length:]); err != nil {
+			return
+		}
+		length += n
+		if length >= asciiMaxSize || n == 0 {
+			break
+		}
+		// Expect end of frame in the data received
+		if length > asciiMinSize {
+			if string(data[length-len(asciiEnd):length]) == asciiEnd {
+				break
+			}
+		}
+	}
+	aduResponse = data[:length]
+	mb.serialPort.logf("modbus: received %q\n", aduResponse)
+	return
+}
+
+// writeHex encodes byte to string in hexadecimal, e.g. 0xA5 => "A5"
+// (encoding/hex only supports lowercase string).
+func writeHex(buf *bytes.Buffer, value []byte) (err error) {
+	var str [2]byte
+	for _, v := range value {
+		str[0] = hexTable[v>>4]
+		str[1] = hexTable[v&0x0F]
+
+		if _, err = buf.Write(str[:]); err != nil {
+			return
+		}
+	}
+	return
+}
+
+// readHex decodes hexa string to byte, e.g. "8C" => 0x8C.
+func readHex(data []byte) (value byte, err error) {
+	var dst [1]byte
+	if _, err = hex.Decode(dst[:], data[0:2]); err != nil {
+		return
+	}
+	value = dst[0]
+	return
+}

+ 76 - 0
SERVER/Meter_Service/core/modbus/asciiclient_test.go

@@ -0,0 +1,76 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestASCIIEncoding(t *testing.T) {
+	encoder := asciiPackager{}
+	encoder.SlaveId = 17
+
+	pdu := ProtocolDataUnit{}
+	pdu.FunctionCode = 3
+	pdu.Data = []byte{0, 107, 0, 3}
+
+	adu, err := encoder.Encode(&pdu)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := []byte(":1103006B00037E\r\n")
+	if !bytes.Equal(expected, adu) {
+		t.Fatalf("adu actual: %v, expected %v", adu, expected)
+	}
+}
+
+func TestASCIIDecoding(t *testing.T) {
+	decoder := asciiPackager{}
+	decoder.SlaveId = 247
+	adu := []byte(":F7031389000A60\r\n")
+
+	pdu, err := decoder.Decode(adu)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if 3 != pdu.FunctionCode {
+		t.Fatalf("Function code: expected %v, actual %v", 15, pdu.FunctionCode)
+	}
+	expected := []byte{0x13, 0x89, 0, 0x0A}
+	if !bytes.Equal(expected, pdu.Data) {
+		t.Fatalf("Data: expected %v, actual %v", expected, pdu.Data)
+	}
+}
+
+func BenchmarkASCIIEncoder(b *testing.B) {
+	encoder := asciiPackager{
+		SlaveId: 10,
+	}
+	pdu := ProtocolDataUnit{
+		FunctionCode: 1,
+		Data:         []byte{2, 3, 4, 5, 6, 7, 8, 9},
+	}
+	for i := 0; i < b.N; i++ {
+		_, err := encoder.Encode(&pdu)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkASCIIDecoder(b *testing.B) {
+	decoder := asciiPackager{
+		SlaveId: 10,
+	}
+	adu := []byte(":F7031389000A60\r\n")
+	for i := 0; i < b.N; i++ {
+		_, err := decoder.Decode(adu)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}

+ 602 - 0
SERVER/Meter_Service/core/modbus/client.go

@@ -0,0 +1,602 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"encoding/binary"
+	"fmt"
+)
+
+// ClientHandler is the interface that groups the Packager and Transporter methods.
+type ClientHandler interface {
+	Packager
+	Transporter
+}
+
+type client struct {
+	packager    Packager
+	transporter Transporter
+	aduRequest  []byte
+}
+
+// NewClient creates a new modbus client with given backend handler.
+func NewClient(handler ClientHandler) Client {
+	return &client{packager: handler, transporter: handler}
+}
+
+// NewClient2 creates a new modbus client with given backend packager and transporter.
+func NewClient2(packager Packager, transporter Transporter) Client {
+	return &client{packager: packager, transporter: transporter}
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x01)
+//	Starting address      : 2 bytes
+//	Quantity of coils     : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x01)
+//	Byte count            : 1 byte
+//	Coil status           : N* bytes (=N or N+1)
+func (mb *client) ReadCoils(address, quantity uint16) (results []byte, err error) {
+	if quantity < 1 || quantity > 2000 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 2000)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeReadCoils,
+		Data:         dataBlock(address, quantity),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	count := int(response.Data[0])
+	length := len(response.Data) - 1
+	if count != length {
+		err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
+		return
+	}
+	results = response.Data[1:]
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x02)
+//	Starting address      : 2 bytes
+//	Quantity of inputs    : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x02)
+//	Byte count            : 1 byte
+//	Input status          : N* bytes (=N or N+1)
+func (mb *client) ReadDiscreteInputs(address, quantity uint16) (results []byte, err error) {
+	if quantity < 1 || quantity > 2000 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 2000)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeReadDiscreteInputs,
+		Data:         dataBlock(address, quantity),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	count := int(response.Data[0])
+	length := len(response.Data) - 1
+	if count != length {
+		err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
+		return
+	}
+	results = response.Data[1:]
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x03)
+//	Starting address      : 2 bytes
+//	Quantity of registers : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x03)
+//	Byte count            : 1 byte
+//	Register value        : Nx2 bytes
+func (mb *client) PackReadHoldingRegisters(address, quantity uint16) (results []byte, err error) {
+	if quantity < 1 || quantity > 125 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeReadHoldingRegisters,
+		Data:         dataBlock(address, quantity),
+	}
+	response, err := mb.pack_post(&request)
+	if err != nil {
+		return
+	}
+	//存储要发送的字节数组,用于接收数据时验证
+	mb.aduRequest = response
+	results = response
+	return
+}
+
+func (mb *client) DePackRegisters(aduResponse []byte) (results []byte, err error) {
+	response, err := mb.depack_rcv(mb.aduRequest, aduResponse)
+	if err != nil {
+		return
+	}
+	count := int(response.Data[0])
+	length := len(response.Data) - 1
+	if count != length {
+		err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
+		return
+	}
+	results = response.Data[1:]
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x03)
+//	Starting address      : 2 bytes
+//	Quantity of registers : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x03)
+//	Byte count            : 1 byte
+//	Register value        : Nx2 bytes
+func (mb *client) ReadHoldingRegisters(address, quantity uint16) (results []byte, err error) {
+	if quantity < 1 || quantity > 125 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeReadHoldingRegisters,
+		Data:         dataBlock(address, quantity),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	count := int(response.Data[0])
+	length := len(response.Data) - 1
+	if count != length {
+		err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
+		return
+	}
+	results = response.Data[1:]
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x04)
+//	Starting address      : 2 bytes
+//	Quantity of registers : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x04)
+//	Byte count            : 1 byte
+//	Input registers       : N bytes
+func (mb *client) ReadInputRegisters(address, quantity uint16) (results []byte, err error) {
+	if quantity < 1 || quantity > 125 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeReadInputRegisters,
+		Data:         dataBlock(address, quantity),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	count := int(response.Data[0])
+	length := len(response.Data) - 1
+	if count != length {
+		err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
+		return
+	}
+	results = response.Data[1:]
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x05)
+//	Output address        : 2 bytes
+//	Output value          : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x05)
+//	Output address        : 2 bytes
+//	Output value          : 2 bytes
+func (mb *client) WriteSingleCoil(address, value uint16) (results []byte, err error) {
+	// The requested ON/OFF state can only be 0xFF00 and 0x0000
+	if value != 0xFF00 && value != 0x0000 {
+		err = fmt.Errorf("modbus: state '%v' must be either 0xFF00 (ON) or 0x0000 (OFF)", value)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeWriteSingleCoil,
+		Data:         dataBlock(address, value),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	// Fixed response length
+	if len(response.Data) != 4 {
+		err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
+		return
+	}
+	respValue := binary.BigEndian.Uint16(response.Data)
+	if address != respValue {
+		err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
+		return
+	}
+	results = response.Data[2:]
+	respValue = binary.BigEndian.Uint16(results)
+	if value != respValue {
+		err = fmt.Errorf("modbus: response value '%v' does not match request '%v'", respValue, value)
+		return
+	}
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x06)
+//	Register address      : 2 bytes
+//	Register value        : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x06)
+//	Register address      : 2 bytes
+//	Register value        : 2 bytes
+func (mb *client) WriteSingleRegister(address, value uint16) (results []byte, err error) {
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeWriteSingleRegister,
+		Data:         dataBlock(address, value),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	// Fixed response length
+	if len(response.Data) != 4 {
+		err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
+		return
+	}
+	respValue := binary.BigEndian.Uint16(response.Data)
+	if address != respValue {
+		err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
+		return
+	}
+	results = response.Data[2:]
+	respValue = binary.BigEndian.Uint16(results)
+	if value != respValue {
+		err = fmt.Errorf("modbus: response value '%v' does not match request '%v'", respValue, value)
+		return
+	}
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x0F)
+//	Starting address      : 2 bytes
+//	Quantity of outputs   : 2 bytes
+//	Byte count            : 1 byte
+//	Outputs value         : N* bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x0F)
+//	Starting address      : 2 bytes
+//	Quantity of outputs   : 2 bytes
+func (mb *client) WriteMultipleCoils(address, quantity uint16, value []byte) (results []byte, err error) {
+	if quantity < 1 || quantity > 1968 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 1968)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeWriteMultipleCoils,
+		Data:         dataBlockSuffix(value, address, quantity),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	// Fixed response length
+	if len(response.Data) != 4 {
+		err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
+		return
+	}
+	respValue := binary.BigEndian.Uint16(response.Data)
+	if address != respValue {
+		err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
+		return
+	}
+	results = response.Data[2:]
+	respValue = binary.BigEndian.Uint16(results)
+	if quantity != respValue {
+		err = fmt.Errorf("modbus: response quantity '%v' does not match request '%v'", respValue, quantity)
+		return
+	}
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x10)
+//	Starting address      : 2 bytes
+//	Quantity of outputs   : 2 bytes
+//	Byte count            : 1 byte
+//	Registers value       : N* bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x10)
+//	Starting address      : 2 bytes
+//	Quantity of registers : 2 bytes
+func (mb *client) WriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error) {
+	if quantity < 1 || quantity > 123 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 123)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeWriteMultipleRegisters,
+		Data:         dataBlockSuffix(value, address, quantity),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	// Fixed response length
+	if len(response.Data) != 4 {
+		err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
+		return
+	}
+	respValue := binary.BigEndian.Uint16(response.Data)
+	if address != respValue {
+		err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
+		return
+	}
+	results = response.Data[2:]
+	respValue = binary.BigEndian.Uint16(results)
+	if quantity != respValue {
+		err = fmt.Errorf("modbus: response quantity '%v' does not match request '%v'", respValue, quantity)
+		return
+	}
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x16)
+//	Reference address     : 2 bytes
+//	AND-mask              : 2 bytes
+//	OR-mask               : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x16)
+//	Reference address     : 2 bytes
+//	AND-mask              : 2 bytes
+//	OR-mask               : 2 bytes
+func (mb *client) MaskWriteRegister(address, andMask, orMask uint16) (results []byte, err error) {
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeMaskWriteRegister,
+		Data:         dataBlock(address, andMask, orMask),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	// Fixed response length
+	if len(response.Data) != 6 {
+		err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 6)
+		return
+	}
+	respValue := binary.BigEndian.Uint16(response.Data)
+	if address != respValue {
+		err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
+		return
+	}
+	respValue = binary.BigEndian.Uint16(response.Data[2:])
+	if andMask != respValue {
+		err = fmt.Errorf("modbus: response AND-mask '%v' does not match request '%v'", respValue, andMask)
+		return
+	}
+	respValue = binary.BigEndian.Uint16(response.Data[4:])
+	if orMask != respValue {
+		err = fmt.Errorf("modbus: response OR-mask '%v' does not match request '%v'", respValue, orMask)
+		return
+	}
+	results = response.Data[2:]
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x17)
+//	Read starting address : 2 bytes
+//	Quantity to read      : 2 bytes
+//	Write starting address: 2 bytes
+//	Quantity to write     : 2 bytes
+//	Write byte count      : 1 byte
+//	Write registers value : N* bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x17)
+//	Byte count            : 1 byte
+//	Read registers value  : Nx2 bytes
+func (mb *client) ReadWriteMultipleRegisters(readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) (results []byte, err error) {
+	if readQuantity < 1 || readQuantity > 125 {
+		err = fmt.Errorf("modbus: quantity to read '%v' must be between '%v' and '%v',", readQuantity, 1, 125)
+		return
+	}
+	if writeQuantity < 1 || writeQuantity > 121 {
+		err = fmt.Errorf("modbus: quantity to write '%v' must be between '%v' and '%v',", writeQuantity, 1, 121)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeReadWriteMultipleRegisters,
+		Data:         dataBlockSuffix(value, readAddress, readQuantity, writeAddress, writeQuantity),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	count := int(response.Data[0])
+	if count != (len(response.Data) - 1) {
+		err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", len(response.Data)-1, count)
+		return
+	}
+	results = response.Data[1:]
+	return
+}
+
+// Request:
+//
+//	Function code         : 1 byte (0x18)
+//	FIFO pointer address  : 2 bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x18)
+//	Byte count            : 2 bytes
+//	FIFO count            : 2 bytes
+//	FIFO count            : 2 bytes (<=31)
+//	FIFO value register   : Nx2 bytes
+func (mb *client) ReadFIFOQueue(address uint16) (results []byte, err error) {
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeReadFIFOQueue,
+		Data:         dataBlock(address),
+	}
+	response, err := mb.send(&request)
+	if err != nil {
+		return
+	}
+	if len(response.Data) < 4 {
+		err = fmt.Errorf("modbus: response data size '%v' is less than expected '%v'", len(response.Data), 4)
+		return
+	}
+	count := int(binary.BigEndian.Uint16(response.Data))
+	if count != (len(response.Data) - 1) {
+		err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", len(response.Data)-1, count)
+		return
+	}
+	count = int(binary.BigEndian.Uint16(response.Data[2:]))
+	if count > 31 {
+		err = fmt.Errorf("modbus: fifo count '%v' is greater than expected '%v'", count, 31)
+		return
+	}
+	results = response.Data[4:]
+	return
+}
+
+// Helpers
+
+func (mb *client) pack_post(request *ProtocolDataUnit) (aduRequest []byte, err error) {
+	aduRequest, err = mb.packager.Encode(request)
+	return
+}
+
+func (mb *client) depack_rcv(aduRequest, aduResponse []byte) (response *ProtocolDataUnit, err error) {
+	var functionCode byte
+	if err = mb.packager.Verify(aduRequest, aduResponse); err != nil {
+		return
+	}
+	//fmt.Println("depack_rcv:", aduResponse)
+	response, err = mb.packager.Decode(aduResponse)
+	if err != nil {
+		return
+	}
+	functionCode = aduRequest[1]
+	// Check correct function code returned (exception)
+	if response.FunctionCode != functionCode {
+		err = responseError(response)
+		return
+	}
+	if response.Data == nil || len(response.Data) == 0 {
+		// Empty response
+		err = fmt.Errorf("modbus: response data is empty")
+		return
+	}
+	return
+}
+
+// send sends request and checks possible exception in the response.
+func (mb *client) send(request *ProtocolDataUnit) (response *ProtocolDataUnit, err error) {
+	aduRequest, err := mb.packager.Encode(request)
+	if err != nil {
+		return
+	}
+	aduResponse, err := mb.transporter.Send(aduRequest)
+	if err != nil {
+		return
+	}
+	if err = mb.packager.Verify(aduRequest, aduResponse); err != nil {
+		return
+	}
+	response, err = mb.packager.Decode(aduResponse)
+	if err != nil {
+		return
+	}
+	// Check correct function code returned (exception)
+	if response.FunctionCode != request.FunctionCode {
+		err = responseError(response)
+		return
+	}
+	if response.Data == nil || len(response.Data) == 0 {
+		// Empty response
+		err = fmt.Errorf("modbus: response data is empty")
+		return
+	}
+	return
+}
+
+// dataBlock creates a sequence of uint16 data.
+func dataBlock(value ...uint16) []byte {
+	data := make([]byte, 2*len(value))
+	for i, v := range value {
+		binary.BigEndian.PutUint16(data[i*2:], v)
+	}
+	return data
+}
+
+// dataBlockSuffix creates a sequence of uint16 data and append the suffix plus its length.
+func dataBlockSuffix(suffix []byte, value ...uint16) []byte {
+	length := 2 * len(value)
+	data := make([]byte, length+1+len(suffix))
+	for i, v := range value {
+		binary.BigEndian.PutUint16(data[i*2:], v)
+	}
+	data[length] = uint8(len(suffix))
+	copy(data[length+1:], suffix)
+	return data
+}
+
+func responseError(response *ProtocolDataUnit) error {
+	mbError := &ModbusError{FunctionCode: response.FunctionCode}
+	if response.Data != nil && len(response.Data) > 0 {
+		mbError.ExceptionCode = response.Data[0]
+	}
+	return mbError
+}

+ 72 - 0
SERVER/Meter_Service/core/modbus/crc.go

@@ -0,0 +1,72 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+// Table of CRC values for high–order byte
+var crcHighBytes = []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,
+}
+
+// Table of CRC values for low-order byte
+var crcLowBytes = []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,
+}
+
+// Cyclical Redundancy Checking
+type crc struct {
+	high byte
+	low  byte
+}
+
+func (crc *crc) reset() *crc {
+	crc.high = 0xFF
+	crc.low = 0xFF
+	return crc
+}
+
+func (crc *crc) pushBytes(bs []byte) *crc {
+	var idx, b byte
+
+	for _, b = range bs {
+		idx = crc.low ^ b
+		crc.low = crc.high ^ crcHighBytes[idx]
+		crc.high = crcLowBytes[idx]
+	}
+	return crc
+}
+
+func (crc *crc) value() uint16 {
+	return uint16(crc.high)<<8 | uint16(crc.low)
+}

+ 19 - 0
SERVER/Meter_Service/core/modbus/crc_test.go

@@ -0,0 +1,19 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"testing"
+)
+
+func TestCRC(t *testing.T) {
+	var crc crc
+	crc.reset()
+	crc.pushBytes([]byte{0x02, 0x07})
+
+	if 0x1241 != crc.value() {
+		t.Fatalf("crc expected %v, actual %v", 0x1241, crc.value())
+	}
+}

+ 33 - 0
SERVER/Meter_Service/core/modbus/lrc.go

@@ -0,0 +1,33 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+// Longitudinal Redundancy Checking
+type lrc struct {
+	sum uint8
+}
+
+func (lrc *lrc) reset() *lrc {
+	lrc.sum = 0
+	return lrc
+}
+
+func (lrc *lrc) pushByte(b byte) *lrc {
+	lrc.sum += b
+	return lrc
+}
+
+func (lrc *lrc) pushBytes(data []byte) *lrc {
+	var b byte
+	for _, b = range data {
+		lrc.sum += b
+	}
+	return lrc
+}
+
+func (lrc *lrc) value() byte {
+	// Return twos complement
+	return uint8(-int8(lrc.sum))
+}

+ 19 - 0
SERVER/Meter_Service/core/modbus/lrc_test.go

@@ -0,0 +1,19 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"testing"
+)
+
+func TestLRC(t *testing.T) {
+	var lrc lrc
+	lrc.reset().pushByte(0x01).pushByte(0x03)
+	lrc.pushBytes([]byte{0x01, 0x0A})
+
+	if 0xF1 != lrc.value() {
+		t.Fatalf("lrc expected %v, actual %v", 0xF1, lrc.value())
+	}
+}

+ 93 - 0
SERVER/Meter_Service/core/modbus/modbus.go

@@ -0,0 +1,93 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+/*
+Package modbus provides a client for MODBUS TCP and RTU/ASCII.
+*/
+package modbus
+
+import (
+	"fmt"
+)
+
+const (
+	// Bit access
+	FuncCodeReadDiscreteInputs = 2
+	FuncCodeReadCoils          = 1
+	FuncCodeWriteSingleCoil    = 5
+	FuncCodeWriteMultipleCoils = 15
+
+	// 16-bit access
+	FuncCodeReadInputRegisters         = 4
+	FuncCodeReadHoldingRegisters       = 3
+	FuncCodeWriteSingleRegister        = 6
+	FuncCodeWriteMultipleRegisters     = 16
+	FuncCodeReadWriteMultipleRegisters = 23
+	FuncCodeMaskWriteRegister          = 22
+	FuncCodeReadFIFOQueue              = 24
+)
+
+const (
+	ExceptionCodeIllegalFunction                    = 1
+	ExceptionCodeIllegalDataAddress                 = 2
+	ExceptionCodeIllegalDataValue                   = 3
+	ExceptionCodeServerDeviceFailure                = 4
+	ExceptionCodeAcknowledge                        = 5
+	ExceptionCodeServerDeviceBusy                   = 6
+	ExceptionCodeMemoryParityError                  = 8
+	ExceptionCodeGatewayPathUnavailable             = 10
+	ExceptionCodeGatewayTargetDeviceFailedToRespond = 11
+)
+
+// ModbusError implements error interface.
+type ModbusError struct {
+	FunctionCode  byte
+	ExceptionCode byte
+}
+
+// Error converts known modbus exception code to error message.
+func (e *ModbusError) Error() string {
+	var name string
+	switch e.ExceptionCode {
+	case ExceptionCodeIllegalFunction:
+		name = "illegal function"
+	case ExceptionCodeIllegalDataAddress:
+		name = "illegal data address"
+	case ExceptionCodeIllegalDataValue:
+		name = "illegal data value"
+	case ExceptionCodeServerDeviceFailure:
+		name = "server device failure"
+	case ExceptionCodeAcknowledge:
+		name = "acknowledge"
+	case ExceptionCodeServerDeviceBusy:
+		name = "server device busy"
+	case ExceptionCodeMemoryParityError:
+		name = "memory parity error"
+	case ExceptionCodeGatewayPathUnavailable:
+		name = "gateway path unavailable"
+	case ExceptionCodeGatewayTargetDeviceFailedToRespond:
+		name = "gateway target device failed to respond"
+	default:
+		name = "unknown"
+	}
+	return fmt.Sprintf("modbus: exception '%v' (%s), function '%v'", e.ExceptionCode, name, e.FunctionCode)
+}
+
+// ProtocolDataUnit (PDU) is independent of underlying communication layers.
+type ProtocolDataUnit struct {
+	FunctionCode byte
+	Data         []byte
+}
+
+// Packager specifies the communication layer.
+type Packager interface {
+	Encode(pdu *ProtocolDataUnit) (adu []byte, err error)
+	Decode(adu []byte) (pdu *ProtocolDataUnit, err error)
+	Verify(aduRequest []byte, aduResponse []byte) (err error)
+}
+
+// Transporter specifies the transport layer.
+type Transporter interface {
+	Send(aduRequest []byte) (aduResponse []byte, err error)
+}

+ 212 - 0
SERVER/Meter_Service/core/modbus/rtuclient.go

@@ -0,0 +1,212 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io"
+	"time"
+)
+
+const (
+	rtuMinSize = 4
+	rtuMaxSize = 256
+
+	rtuExceptionSize = 5
+)
+
+// RTUClientHandler implements Packager and Transporter interface.
+type RTUClientHandler struct {
+	rtuPackager
+	rtuSerialTransporter
+}
+
+// NewRTUClientHandler allocates and initializes a RTUClientHandler.
+func NewRTUClientHandler(address string) *RTUClientHandler {
+	handler := &RTUClientHandler{}
+	handler.Address = address
+	handler.Timeout = serialTimeout
+	handler.IdleTimeout = serialIdleTimeout
+	return handler
+}
+
+// RTUClient creates RTU client with default handler and given connect string.
+func RTUClient(address string) Client {
+	handler := NewRTUClientHandler(address)
+	return NewClient(handler)
+}
+
+// rtuPackager implements Packager interface.
+type rtuPackager struct {
+	SlaveId byte
+}
+
+// Encode encodes PDU in a RTU frame:
+//
+//	Slave Address   : 1 byte
+//	Function        : 1 byte
+//	Data            : 0 up to 252 bytes
+//	CRC             : 2 byte
+func (mb *rtuPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) {
+	length := len(pdu.Data) + 4
+	if length > rtuMaxSize {
+		err = fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'", length, rtuMaxSize)
+		return
+	}
+	adu = make([]byte, length)
+
+	adu[0] = mb.SlaveId
+	adu[1] = pdu.FunctionCode
+	copy(adu[2:], pdu.Data)
+
+	// Append crc
+	var crc crc
+	crc.reset().pushBytes(adu[0 : length-2])
+	checksum := crc.value()
+
+	adu[length-1] = byte(checksum >> 8)
+	adu[length-2] = byte(checksum)
+	return
+}
+
+// Verify verifies response length and slave id.
+func (mb *rtuPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) {
+	length := len(aduResponse)
+	// Minimum size (including address, function and CRC)
+	if length < rtuMinSize {
+		err = fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", length, rtuMinSize)
+		return
+	}
+	// Slave address must match
+	if aduResponse[0] != aduRequest[0] {
+		err = fmt.Errorf("modbus: response slave id '%v' does not match request '%v'", aduResponse[0], aduRequest[0])
+		return
+	}
+	return
+}
+
+// Decode extracts PDU from RTU frame and verify CRC.
+func (mb *rtuPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) {
+	length := len(adu)
+	// Calculate checksum
+	var crc crc
+	crc.reset().pushBytes(adu[0 : length-2])
+	checksum := uint16(adu[length-1])<<8 | uint16(adu[length-2])
+	//fmt.Println("Decode:", adu, ";length=", length)
+	if checksum != crc.value() {
+		err = fmt.Errorf("modbus-rtu: response crc '%v' does not match expected '%v'", checksum, crc.value())
+		return
+	}
+	// Function code & data
+	pdu = &ProtocolDataUnit{}
+	pdu.FunctionCode = adu[1]
+	pdu.Data = adu[2 : length-2]
+	return
+}
+
+// rtuSerialTransporter implements Transporter interface.
+type rtuSerialTransporter struct {
+	serialPort
+}
+
+func (mb *rtuSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) {
+	// Make sure port is connected
+	if err = mb.serialPort.connect(); err != nil {
+		return
+	}
+	// Start the timer to close when idle
+	mb.serialPort.lastActivity = time.Now()
+	mb.serialPort.startCloseTimer()
+
+	// Send the request
+	mb.serialPort.logf("modbus: sending % x\n", aduRequest)
+	if _, err = mb.port.Write(aduRequest); err != nil {
+		return
+	}
+	function := aduRequest[1]
+	functionFail := aduRequest[1] & 0x80
+	bytesToRead := calculateResponseLength(aduRequest)
+	time.Sleep(mb.calculateDelay(len(aduRequest) + bytesToRead))
+
+	var n int
+	var n1 int
+	var data [rtuMaxSize]byte
+	//We first read the minimum length and then read either the full package
+	//or the error package, depending on the error status (byte 2 of the response)
+	n, err = io.ReadAtLeast(mb.port, data[:], rtuMinSize)
+	if err != nil {
+		return
+	}
+	//if the function is correct
+	if data[1] == function {
+		//we read the rest of the bytes
+		if n < bytesToRead {
+			if bytesToRead > rtuMinSize && bytesToRead <= rtuMaxSize {
+				if bytesToRead > n {
+					n1, err = io.ReadFull(mb.port, data[n:bytesToRead])
+					n += n1
+				}
+			}
+		}
+	} else if data[1] == functionFail {
+		//for error we need to read 5 bytes
+		if n < rtuExceptionSize {
+			n1, err = io.ReadFull(mb.port, data[n:rtuExceptionSize])
+		}
+		n += n1
+	}
+
+	if err != nil {
+		return
+	}
+	aduResponse = data[:n]
+	mb.serialPort.logf("modbus: received % x\n", aduResponse)
+	return
+}
+
+// calculateDelay roughly calculates time needed for the next frame.
+// See MODBUS over Serial Line - Specification and Implementation Guide (page 13).
+func (mb *rtuSerialTransporter) calculateDelay(chars int) time.Duration {
+	var characterDelay, frameDelay int // us
+
+	if mb.BaudRate <= 0 || mb.BaudRate > 19200 {
+		characterDelay = 750
+		frameDelay = 1750
+	} else {
+		characterDelay = 15000000 / mb.BaudRate
+		frameDelay = 35000000 / mb.BaudRate
+	}
+	return time.Duration(characterDelay*chars+frameDelay) * time.Microsecond
+}
+
+func calculateResponseLength(adu []byte) int {
+	length := rtuMinSize
+	switch adu[1] {
+	case FuncCodeReadDiscreteInputs,
+		FuncCodeReadCoils:
+		count := int(binary.BigEndian.Uint16(adu[4:]))
+		length += 1 + count/8
+		if count%8 != 0 {
+			length++
+		}
+	case FuncCodeReadInputRegisters,
+		FuncCodeReadHoldingRegisters,
+		FuncCodeReadWriteMultipleRegisters:
+		count := int(binary.BigEndian.Uint16(adu[4:]))
+		length += 1 + count*2
+	case FuncCodeWriteSingleCoil,
+		FuncCodeWriteMultipleCoils,
+		FuncCodeWriteSingleRegister,
+		FuncCodeWriteMultipleRegisters:
+		length += 4
+	case FuncCodeMaskWriteRegister:
+		length += 6
+	case FuncCodeReadFIFOQueue:
+		// undetermined
+	default:
+	}
+	return length
+}

+ 98 - 0
SERVER/Meter_Service/core/modbus/rtuclient_test.go

@@ -0,0 +1,98 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestRTUEncoding(t *testing.T) {
+	encoder := rtuPackager{}
+	encoder.SlaveId = 0x01
+
+	pdu := ProtocolDataUnit{}
+	pdu.FunctionCode = 0x03
+	pdu.Data = []byte{0x50, 0x00, 0x00, 0x18}
+
+	adu, err := encoder.Encode(&pdu)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := []byte{0x01, 0x03, 0x50, 0x00, 0x00, 0x18, 0x54, 0xC0}
+	if !bytes.Equal(expected, adu) {
+		t.Fatalf("adu: expected %v, actual %v", expected, adu)
+	}
+}
+
+func TestRTUDecoding(t *testing.T) {
+	decoder := rtuPackager{}
+	adu := []byte{0x01, 0x10, 0x8A, 0x00, 0x00, 0x03, 0xAA, 0x10}
+
+	pdu, err := decoder.Decode(adu)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if 16 != pdu.FunctionCode {
+		t.Fatalf("Function code: expected %v, actual %v", 16, pdu.FunctionCode)
+	}
+	expected := []byte{0x8A, 0x00, 0x00, 0x03}
+	if !bytes.Equal(expected, pdu.Data) {
+		t.Fatalf("Data: expected %v, actual %v", expected, pdu.Data)
+	}
+}
+
+var responseLengthTests = []struct {
+	adu    []byte
+	length int
+}{
+	{[]byte{4, 1, 0, 0xA, 0, 0xD, 0xDD, 0x98}, 7},
+	{[]byte{4, 2, 0, 0xA, 0, 0xD, 0x99, 0x98}, 7},
+	{[]byte{1, 3, 0, 0, 0, 2, 0xC4, 0xB}, 9},
+	{[]byte{0x11, 5, 0, 0xAC, 0xFF, 0, 0x4E, 0x8B}, 8},
+	{[]byte{0x11, 6, 0, 1, 0, 3, 0x9A, 0x9B}, 8},
+	{[]byte{0x11, 0xF, 0, 0x13, 0, 0xA, 2, 0xCD, 1, 0xBF, 0xB}, 8},
+	{[]byte{0x11, 0x10, 0, 1, 0, 2, 4, 0, 0xA, 1, 2, 0xC6, 0xF0}, 8},
+}
+
+func TestCalculateResponseLength(t *testing.T) {
+	for _, input := range responseLengthTests {
+		output := calculateResponseLength(input.adu)
+		if output != input.length {
+			t.Errorf("Response length of %x: expected %v, actual: %v",
+				input.adu, input.length, output)
+		}
+	}
+}
+
+func BenchmarkRTUEncoder(b *testing.B) {
+	encoder := rtuPackager{
+		SlaveId: 10,
+	}
+	pdu := ProtocolDataUnit{
+		FunctionCode: 1,
+		Data:         []byte{2, 3, 4, 5, 6, 7, 8, 9},
+	}
+	for i := 0; i < b.N; i++ {
+		_, err := encoder.Encode(&pdu)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkRTUDecoder(b *testing.B) {
+	decoder := rtuPackager{
+		SlaveId: 10,
+	}
+	adu := []byte{0x01, 0x10, 0x8A, 0x00, 0x00, 0x03, 0xAA, 0x10}
+	for i := 0; i < b.N; i++ {
+		_, err := decoder.Decode(adu)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}

+ 102 - 0
SERVER/Meter_Service/core/modbus/serial.go

@@ -0,0 +1,102 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"io"
+	"log"
+	"sync"
+	"time"
+
+	"github.com/goburrow/serial"
+)
+
+const (
+	// Default timeout
+	serialTimeout     = 5 * time.Second
+	serialIdleTimeout = 60 * time.Second
+)
+
+// serialPort has configuration and I/O controller.
+type serialPort struct {
+	// Serial port configuration.
+	serial.Config
+
+	Logger      *log.Logger
+	IdleTimeout time.Duration
+
+	mu sync.Mutex
+	// port is platform-dependent data structure for serial port.
+	port         io.ReadWriteCloser
+	lastActivity time.Time
+	closeTimer   *time.Timer
+}
+
+func (mb *serialPort) Connect() (err error) {
+	mb.mu.Lock()
+	defer mb.mu.Unlock()
+
+	return mb.connect()
+}
+
+// connect connects to the serial port if it is not connected. Caller must hold the mutex.
+func (mb *serialPort) connect() error {
+	if mb.port == nil {
+		port, err := serial.Open(&mb.Config)
+		if err != nil {
+			return err
+		}
+		mb.port = port
+	}
+	return nil
+}
+
+func (mb *serialPort) Close() (err error) {
+	mb.mu.Lock()
+	defer mb.mu.Unlock()
+
+	return mb.close()
+}
+
+// close closes the serial port if it is connected. Caller must hold the mutex.
+func (mb *serialPort) close() (err error) {
+	if mb.port != nil {
+		err = mb.port.Close()
+		mb.port = nil
+	}
+	return
+}
+
+func (mb *serialPort) logf(format string, v ...interface{}) {
+	if mb.Logger != nil {
+		mb.Logger.Printf(format, v...)
+	}
+}
+
+func (mb *serialPort) startCloseTimer() {
+	if mb.IdleTimeout <= 0 {
+		return
+	}
+	if mb.closeTimer == nil {
+		mb.closeTimer = time.AfterFunc(mb.IdleTimeout, mb.closeIdle)
+	} else {
+		mb.closeTimer.Reset(mb.IdleTimeout)
+	}
+}
+
+// closeIdle closes the connection if last activity is passed behind IdleTimeout.
+func (mb *serialPort) closeIdle() {
+	mb.mu.Lock()
+	defer mb.mu.Unlock()
+
+	if mb.IdleTimeout <= 0 {
+		return
+	}
+	idle := time.Now().Sub(mb.lastActivity)
+	if idle >= mb.IdleTimeout {
+		mb.logf("modbus: closing connection due to idle timeout: %v", idle)
+		mb.close()
+	}
+}

+ 36 - 0
SERVER/Meter_Service/core/modbus/serial_test.go

@@ -0,0 +1,36 @@
+package modbus
+
+import (
+	"bytes"
+	"io"
+	"testing"
+	"time"
+)
+
+type nopCloser struct {
+	io.ReadWriter
+
+	closed bool
+}
+
+func (n *nopCloser) Close() error {
+	n.closed = true
+	return nil
+}
+
+func TestSerialCloseIdle(t *testing.T) {
+	port := &nopCloser{
+		ReadWriter: &bytes.Buffer{},
+	}
+	s := serialPort{
+		port:        port,
+		IdleTimeout: 100 * time.Millisecond,
+	}
+	s.lastActivity = time.Now()
+	s.startCloseTimer()
+
+	time.Sleep(150 * time.Millisecond)
+	if !port.closed || s.port != nil {
+		t.Fatalf("serial port is not closed when inactivity: %+v", port)
+	}
+}

+ 285 - 0
SERVER/Meter_Service/core/modbus/tcpclient.go

@@ -0,0 +1,285 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io"
+	"log"
+	"net"
+	"sync"
+	"sync/atomic"
+	"time"
+)
+
+const (
+	tcpProtocolIdentifier uint16 = 0x0000
+
+	// Modbus Application Protocol
+	tcpHeaderSize = 7
+	tcpMaxLength  = 260
+	// Default TCP timeout is not set
+	tcpTimeout     = 10 * time.Second
+	tcpIdleTimeout = 60 * time.Second
+)
+
+// TCPClientHandler implements Packager and Transporter interface.
+type TCPClientHandler struct {
+	tcpPackager
+	tcpTransporter
+}
+
+// NewTCPClientHandler allocates a new TCPClientHandler.
+func NewTCPClientHandler(address string) *TCPClientHandler {
+	h := &TCPClientHandler{}
+	h.Address = address
+	h.Timeout = tcpTimeout
+	h.IdleTimeout = tcpIdleTimeout
+	return h
+}
+
+// TCPClient creates TCP client with default handler and given connect string.
+func TCPClient(address string) Client {
+	handler := NewTCPClientHandler(address)
+	return NewClient(handler)
+}
+
+// tcpPackager implements Packager interface.
+type tcpPackager struct {
+	// For synchronization between messages of server & client
+	transactionId uint32
+	// Broadcast address is 0
+	SlaveId byte
+}
+
+// Encode adds modbus application protocol header:
+//
+//	Transaction identifier: 2 bytes
+//	Protocol identifier: 2 bytes
+//	Length: 2 bytes
+//	Unit identifier: 1 byte
+//	Function code: 1 byte
+//	Data: n bytes
+func (mb *tcpPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) {
+	adu = make([]byte, tcpHeaderSize+1+len(pdu.Data))
+
+	// Transaction identifier
+	transactionId := atomic.AddUint32(&mb.transactionId, 1)
+	binary.BigEndian.PutUint16(adu, uint16(transactionId))
+	// Protocol identifier
+	binary.BigEndian.PutUint16(adu[2:], tcpProtocolIdentifier)
+	// Length = sizeof(SlaveId) + sizeof(FunctionCode) + Data
+	length := uint16(1 + 1 + len(pdu.Data))
+	binary.BigEndian.PutUint16(adu[4:], length)
+	// Unit identifier
+	adu[6] = mb.SlaveId
+
+	// PDU
+	adu[tcpHeaderSize] = pdu.FunctionCode
+	copy(adu[tcpHeaderSize+1:], pdu.Data)
+	return
+}
+
+// Verify confirms transaction, protocol and unit id.
+func (mb *tcpPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) {
+	// Transaction id
+	responseVal := binary.BigEndian.Uint16(aduResponse)
+	requestVal := binary.BigEndian.Uint16(aduRequest)
+	if responseVal != requestVal {
+		err = fmt.Errorf("modbus: response transaction id '%v' does not match request '%v'", responseVal, requestVal)
+		return
+	}
+	// Protocol id
+	responseVal = binary.BigEndian.Uint16(aduResponse[2:])
+	requestVal = binary.BigEndian.Uint16(aduRequest[2:])
+	if responseVal != requestVal {
+		err = fmt.Errorf("modbus: response protocol id '%v' does not match request '%v'", responseVal, requestVal)
+		return
+	}
+	// Unit id (1 byte)
+	if aduResponse[6] != aduRequest[6] {
+		err = fmt.Errorf("modbus: response unit id '%v' does not match request '%v'", aduResponse[6], aduRequest[6])
+		return
+	}
+	return
+}
+
+// Decode extracts PDU from TCP frame:
+//
+//	Transaction identifier: 2 bytes
+//	Protocol identifier: 2 bytes
+//	Length: 2 bytes
+//	Unit identifier: 1 byte
+func (mb *tcpPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) {
+	// Read length value in the header
+	length := binary.BigEndian.Uint16(adu[4:])
+	pduLength := len(adu) - tcpHeaderSize
+	if pduLength <= 0 || pduLength != int(length-1) {
+		err = fmt.Errorf("modbus: length in response '%v' does not match pdu data length '%v'", length-1, pduLength)
+		return
+	}
+	pdu = &ProtocolDataUnit{}
+	// The first byte after header is function code
+	pdu.FunctionCode = adu[tcpHeaderSize]
+	pdu.Data = adu[tcpHeaderSize+1:]
+	return
+}
+
+// tcpTransporter implements Transporter interface.
+type tcpTransporter struct {
+	// Connect string
+	Address string
+	// Connect & Read timeout
+	Timeout time.Duration
+	// Idle timeout to close the connection
+	IdleTimeout time.Duration
+	// Transmission logger
+	Logger *log.Logger
+
+	// TCP connection
+	mu           sync.Mutex
+	conn         net.Conn
+	closeTimer   *time.Timer
+	lastActivity time.Time
+}
+
+// Send sends data to server and ensures response length is greater than header length.
+func (mb *tcpTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) {
+	mb.mu.Lock()
+	defer mb.mu.Unlock()
+
+	// Establish a new connection if not connected
+	if err = mb.connect(); err != nil {
+		return
+	}
+	// Set timer to close when idle
+	mb.lastActivity = time.Now()
+	mb.startCloseTimer()
+	// Set write and read timeout
+	var timeout time.Time
+	if mb.Timeout > 0 {
+		timeout = mb.lastActivity.Add(mb.Timeout)
+	}
+	if err = mb.conn.SetDeadline(timeout); err != nil {
+		return
+	}
+	// Send data
+	mb.logf("modbus: sending % x", aduRequest)
+	if _, err = mb.conn.Write(aduRequest); err != nil {
+		return
+	}
+	// Read header first
+	var data [tcpMaxLength]byte
+	if _, err = io.ReadFull(mb.conn, data[:tcpHeaderSize]); err != nil {
+		return
+	}
+	// Read length, ignore transaction & protocol id (4 bytes)
+	length := int(binary.BigEndian.Uint16(data[4:]))
+	if length <= 0 {
+		mb.flush(data[:])
+		err = fmt.Errorf("modbus: length in response header '%v' must not be zero", length)
+		return
+	}
+	if length > (tcpMaxLength - (tcpHeaderSize - 1)) {
+		mb.flush(data[:])
+		err = fmt.Errorf("modbus: length in response header '%v' must not greater than '%v'", length, tcpMaxLength-tcpHeaderSize+1)
+		return
+	}
+	// Skip unit id
+	length += tcpHeaderSize - 1
+	if _, err = io.ReadFull(mb.conn, data[tcpHeaderSize:length]); err != nil {
+		return
+	}
+	aduResponse = data[:length]
+	mb.logf("modbus: received % x\n", aduResponse)
+	return
+}
+
+// Connect establishes a new connection to the address in Address.
+// Connect and Close are exported so that multiple requests can be done with one session
+func (mb *tcpTransporter) Connect() error {
+	mb.mu.Lock()
+	defer mb.mu.Unlock()
+
+	return mb.connect()
+}
+
+func (mb *tcpTransporter) connect() error {
+	if mb.conn == nil {
+		dialer := net.Dialer{Timeout: mb.Timeout}
+		conn, err := dialer.Dial("tcp", mb.Address)
+		if err != nil {
+			return err
+		}
+		mb.conn = conn
+	}
+	return nil
+}
+
+func (mb *tcpTransporter) startCloseTimer() {
+	if mb.IdleTimeout <= 0 {
+		return
+	}
+	if mb.closeTimer == nil {
+		mb.closeTimer = time.AfterFunc(mb.IdleTimeout, mb.closeIdle)
+	} else {
+		mb.closeTimer.Reset(mb.IdleTimeout)
+	}
+}
+
+// Close closes current connection.
+func (mb *tcpTransporter) Close() error {
+	mb.mu.Lock()
+	defer mb.mu.Unlock()
+
+	return mb.close()
+}
+
+// flush flushes pending data in the connection,
+// returns io.EOF if connection is closed.
+func (mb *tcpTransporter) flush(b []byte) (err error) {
+	if err = mb.conn.SetReadDeadline(time.Now()); err != nil {
+		return
+	}
+	// Timeout setting will be reset when reading
+	if _, err = mb.conn.Read(b); err != nil {
+		// Ignore timeout error
+		if netError, ok := err.(net.Error); ok && netError.Timeout() {
+			err = nil
+		}
+	}
+	return
+}
+
+func (mb *tcpTransporter) logf(format string, v ...interface{}) {
+	if mb.Logger != nil {
+		mb.Logger.Printf(format, v...)
+	}
+}
+
+// closeLocked closes current connection. Caller must hold the mutex before calling this method.
+func (mb *tcpTransporter) close() (err error) {
+	if mb.conn != nil {
+		err = mb.conn.Close()
+		mb.conn = nil
+	}
+	return
+}
+
+// closeIdle closes the connection if last activity is passed behind IdleTimeout.
+func (mb *tcpTransporter) closeIdle() {
+	mb.mu.Lock()
+	defer mb.mu.Unlock()
+
+	if mb.IdleTimeout <= 0 {
+		return
+	}
+	idle := time.Now().Sub(mb.lastActivity)
+	if idle >= mb.IdleTimeout {
+		mb.logf("modbus: closing connection due to idle timeout: %v", idle)
+		mb.close()
+	}
+}

+ 118 - 0
SERVER/Meter_Service/core/modbus/tcpclient_test.go

@@ -0,0 +1,118 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license. See the LICENSE file for details.
+
+package modbus
+
+import (
+	"bytes"
+	"io"
+	"net"
+	"testing"
+	"time"
+)
+
+func TestTCPEncoding(t *testing.T) {
+	packager := tcpPackager{}
+	pdu := ProtocolDataUnit{}
+	pdu.FunctionCode = 3
+	pdu.Data = []byte{0, 4, 0, 3}
+
+	adu, err := packager.Encode(&pdu)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := []byte{0, 1, 0, 0, 0, 6, 0, 3, 0, 4, 0, 3}
+	if !bytes.Equal(expected, adu) {
+		t.Fatalf("Expected %v, actual %v", expected, adu)
+	}
+}
+
+func TestTCPDecoding(t *testing.T) {
+	packager := tcpPackager{}
+	packager.transactionId = 1
+	packager.SlaveId = 17
+	adu := []byte{0, 1, 0, 0, 0, 6, 17, 3, 0, 120, 0, 3}
+
+	pdu, err := packager.Decode(adu)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if 3 != pdu.FunctionCode {
+		t.Fatalf("Function code: expected %v, actual %v", 3, pdu.FunctionCode)
+	}
+	expected := []byte{0, 120, 0, 3}
+	if !bytes.Equal(expected, pdu.Data) {
+		t.Fatalf("Data: expected %v, actual %v", expected, adu)
+	}
+}
+
+func TestTCPTransporter(t *testing.T) {
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ln.Close()
+
+	go func() {
+		conn, err := ln.Accept()
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		defer conn.Close()
+		_, err = io.Copy(conn, conn)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+	}()
+	client := &tcpTransporter{
+		Address:     ln.Addr().String(),
+		Timeout:     1 * time.Second,
+		IdleTimeout: 100 * time.Millisecond,
+	}
+	req := []byte{0, 1, 0, 2, 0, 2, 1, 2}
+	rsp, err := client.Send(req)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(req, rsp) {
+		t.Fatalf("unexpected response: %x", rsp)
+	}
+	time.Sleep(150 * time.Millisecond)
+	if client.conn != nil {
+		t.Fatalf("connection is not closed: %+v", client.conn)
+	}
+}
+
+func BenchmarkTCPEncoder(b *testing.B) {
+	encoder := tcpPackager{
+		SlaveId: 10,
+	}
+	pdu := ProtocolDataUnit{
+		FunctionCode: 1,
+		Data:         []byte{2, 3, 4, 5, 6, 7, 8, 9},
+	}
+	for i := 0; i < b.N; i++ {
+		_, err := encoder.Encode(&pdu)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkTCPDecoder(b *testing.B) {
+	decoder := tcpPackager{
+		SlaveId: 10,
+	}
+	adu := []byte{0, 1, 0, 0, 0, 6, 17, 3, 0, 120, 0, 3}
+	for i := 0; i < b.N; i++ {
+		_, err := decoder.Decode(adu)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}

+ 24 - 0
SERVER/Meter_Service/core/modbus/test/README.md

@@ -0,0 +1,24 @@
+System testing for [modbus library](https://github.com/goburrow/modbus)
+
+Modbus simulator
+----------------
+*   [Diagslave](http://www.modbusdriver.com/diagslave.html)
+*   [socat](http://www.dest-unreach.org/socat/)
+
+```bash
+# TCP
+$ diagslave -m tcp -p 5020
+
+# RTU/ASCII
+$ socat -d -d pty,raw,echo=0 pty,raw,echo=0
+2015/04/03 12:34:56 socat[2342] N PTY is /dev/pts/6
+2015/04/03 12:34:56 socat[2342] N PTY is /dev/pts/7
+$ diagslave -m ascii /dev/pts/7
+
+# Or
+$ diagslave -m rtu /dev/pts/7
+
+$ go test -v -run TCP
+$ go test -v -run RTU
+$ go test -v -run ASCII
+```

+ 49 - 0
SERVER/Meter_Service/core/modbus/test/asciiclient_test.go

@@ -0,0 +1,49 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license.  See the LICENSE file for details.
+
+package test
+
+import (
+	"log"
+	"os"
+	"testing"
+
+	"github.com/goburrow/modbus"
+)
+
+const (
+	asciiDevice = "/dev/pts/6"
+)
+
+func TestASCIIClient(t *testing.T) {
+	// Diagslave does not support broadcast id.
+	handler := modbus.NewASCIIClientHandler(asciiDevice)
+	handler.SlaveId = 17
+	ClientTestAll(t, modbus.NewClient(handler))
+}
+
+func TestASCIIClientAdvancedUsage(t *testing.T) {
+	handler := modbus.NewASCIIClientHandler(asciiDevice)
+	handler.BaudRate = 19200
+	handler.DataBits = 8
+	handler.Parity = "E"
+	handler.StopBits = 1
+	handler.SlaveId = 12
+	handler.Logger = log.New(os.Stdout, "ascii: ", log.LstdFlags)
+	err := handler.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer handler.Close()
+
+	client := modbus.NewClient(handler)
+	results, err := client.ReadDiscreteInputs(15, 2)
+	if err != nil || results == nil {
+		t.Fatal(err, results)
+	}
+	results, err = client.ReadWriteMultipleRegisters(0, 2, 2, 2, []byte{1, 2, 3, 4})
+	if err != nil || results == nil {
+		t.Fatal(err, results)
+	}
+}

+ 153 - 0
SERVER/Meter_Service/core/modbus/test/client.go

@@ -0,0 +1,153 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license.  See the LICENSE file for details.
+
+package test
+
+import (
+	"testing"
+
+	"github.com/goburrow/modbus"
+)
+
+func ClientTestReadCoils(t *testing.T, client modbus.Client) {
+	// Read discrete outputs 20-38:
+	address := uint16(0x0013)
+	quantity := uint16(0x0013)
+	results, err := client.ReadCoils(address, quantity)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 3, len(results))
+}
+
+func ClientTestReadDiscreteInputs(t *testing.T, client modbus.Client) {
+	// Read discrete inputs 197-218
+	address := uint16(0x00C4)
+	quantity := uint16(0x0016)
+	results, err := client.ReadDiscreteInputs(address, quantity)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 3, len(results))
+}
+
+func ClientTestReadHoldingRegisters(t *testing.T, client modbus.Client) {
+	// Read registers 108-110
+	address := uint16(0x006B)
+	quantity := uint16(0x0003)
+	results, err := client.ReadHoldingRegisters(address, quantity)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 6, len(results))
+}
+
+func ClientTestReadInputRegisters(t *testing.T, client modbus.Client) {
+	// Read input register 9
+	address := uint16(0x0008)
+	quantity := uint16(0x0001)
+	results, err := client.ReadInputRegisters(address, quantity)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 2, len(results))
+}
+
+func ClientTestWriteSingleCoil(t *testing.T, client modbus.Client) {
+	// Write coil 173 ON
+	address := uint16(0x00AC)
+	value := uint16(0xFF00)
+	results, err := client.WriteSingleCoil(address, value)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 2, len(results))
+}
+
+func ClientTestWriteSingleRegister(t *testing.T, client modbus.Client) {
+	// Write register 2 to 00 03 hex
+	address := uint16(0x0001)
+	value := uint16(0x0003)
+	results, err := client.WriteSingleRegister(address, value)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 2, len(results))
+}
+
+func ClientTestWriteMultipleCoils(t *testing.T, client modbus.Client) {
+	// Write a series of 10 coils starting at coil 20
+	address := uint16(0x0013)
+	quantity := uint16(0x000A)
+	values := []byte{0xCD, 0x01}
+	results, err := client.WriteMultipleCoils(address, quantity, values)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 2, len(results))
+}
+
+func ClientTestWriteMultipleRegisters(t *testing.T, client modbus.Client) {
+	// Write two registers starting at 2 to 00 0A and 01 02 hex
+	address := uint16(0x0001)
+	quantity := uint16(0x0002)
+	values := []byte{0x00, 0x0A, 0x01, 0x02}
+	results, err := client.WriteMultipleRegisters(address, quantity, values)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 2, len(results))
+}
+
+func ClientTestMaskWriteRegisters(t *testing.T, client modbus.Client) {
+	// Mask write to register 5
+	address := uint16(0x0004)
+	andMask := uint16(0x00F2)
+	orMask := uint16(0x0025)
+	results, err := client.MaskWriteRegister(address, andMask, orMask)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 4, len(results))
+}
+
+func ClientTestReadWriteMultipleRegisters(t *testing.T, client modbus.Client) {
+	// read six registers starting at register 4, and to write three registers starting at register 15
+	address := uint16(0x0003)
+	quantity := uint16(0x0006)
+	writeAddress := uint16(0x000E)
+	writeQuantity := uint16(0x0003)
+	values := []byte{0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}
+	results, err := client.ReadWriteMultipleRegisters(address, quantity, writeAddress, writeQuantity, values)
+	if err != nil {
+		t.Fatal(err)
+	}
+	AssertEquals(t, 12, len(results))
+}
+
+func ClientTestReadFIFOQueue(t *testing.T, client modbus.Client) {
+	// Read queue starting at the pointer register 1246
+	address := uint16(0x04DE)
+	results, err := client.ReadFIFOQueue(address)
+	// Server not implemented
+	if err != nil {
+		AssertEquals(t, "modbus: exception '1' (illegal function), function '152'", err.Error())
+	} else {
+		AssertEquals(t, 0, len(results))
+	}
+}
+
+func ClientTestAll(t *testing.T, client modbus.Client) {
+	ClientTestReadCoils(t, client)
+	ClientTestReadDiscreteInputs(t, client)
+	ClientTestReadHoldingRegisters(t, client)
+	ClientTestReadInputRegisters(t, client)
+	ClientTestWriteSingleCoil(t, client)
+	ClientTestWriteSingleRegister(t, client)
+	ClientTestWriteMultipleCoils(t, client)
+	ClientTestWriteMultipleRegisters(t, client)
+	ClientTestMaskWriteRegisters(t, client)
+	ClientTestReadWriteMultipleRegisters(t, client)
+	ClientTestReadFIFOQueue(t, client)
+}

+ 31 - 0
SERVER/Meter_Service/core/modbus/test/common.go

@@ -0,0 +1,31 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license.  See the LICENSE file for details.
+
+package test
+
+import (
+	"runtime"
+	"strings"
+	"testing"
+)
+
+func AssertEquals(t *testing.T, expected, actual interface{}) {
+	_, file, line, ok := runtime.Caller(1)
+	if !ok {
+		file = "???"
+		line = 0
+	} else {
+		// Get file name only
+		idx := strings.LastIndex(file, "/")
+		if idx >= 0 {
+			file = file[idx+1:]
+		}
+	}
+
+	if expected != actual {
+		t.Logf("%s:%d: Expected: %+v (%T), actual: %+v (%T)", file, line,
+			expected, expected, actual, actual)
+		t.FailNow()
+	}
+}

+ 107 - 0
SERVER/Meter_Service/core/modbus/test/commw32/commw32.c

@@ -0,0 +1,107 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license.  See the LICENSE file for details.
+
+// Test serial communication in Win32
+// gcc commw32.c
+#include <stdlib.h>
+#include <stdio.h>
+#include <windows.h>
+
+static const char* port = "COM4";
+
+static printLastError() {
+	char lpBuffer[256] = "?";
+	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
+		NULL,
+		GetLastError(),
+		MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
+		lpBuffer,
+		sizeof(lpBuffer)-1,
+		NULL);
+	printf("%s\n", lpBuffer);
+}
+
+int main(int argc, char** argv) {
+	HANDLE handle;
+	DCB dcb = {0};
+	COMMTIMEOUTS timeouts;
+	DWORD n = 0;
+	char data[512];
+	int i = 0;
+
+	handle = CreateFile(port,
+		GENERIC_READ | GENERIC_WRITE,
+		0,
+		0,
+		OPEN_EXISTING,
+		0,
+		0);
+	if (handle == INVALID_HANDLE_VALUE) {
+		printf("invalid handle %d\n", GetLastError());
+		printLastError();
+		return 1;
+	}
+	printf("handle created %d\n", handle);
+
+	dcb.BaudRate = CBR_9600;
+	dcb.ByteSize = 8;
+	dcb.StopBits = ONESTOPBIT;
+	dcb.Parity = NOPARITY;
+	// No software handshaking
+	dcb.fTXContinueOnXoff = 1;
+	dcb.fOutX = 0;
+	dcb.fInX = 0;
+	// Binary mode
+	dcb.fBinary = 1;
+	// No blocking on errors
+	dcb.fAbortOnError = 0;
+
+	if (!SetCommState(handle, &dcb)) {
+		printf("set comm state error %d\n", GetLastError());
+		printLastError();
+		CloseHandle(handle);
+		return 1;
+	}
+	printf("set comm state succeed\n");
+
+	// time-out between charactor for receiving (ms)
+	timeouts.ReadIntervalTimeout = 1000;
+	timeouts.ReadTotalTimeoutMultiplier = 0;
+	timeouts.ReadTotalTimeoutConstant = 1000;
+	timeouts.WriteTotalTimeoutMultiplier = 0;
+	timeouts.WriteTotalTimeoutConstant = 1000;
+	if (!SetCommTimeouts(handle, &timeouts)) {
+		printf("set comm timeouts error %d\n", GetLastError());
+		printLastError();
+		CloseHandle(handle);
+		return 1;
+	}
+	printf("set comm timeouts succeed\n");
+
+	if (!WriteFile(handle, "abc", 3, &n, NULL)) {
+		printf("write file error %d\n", GetLastError());
+		printLastError();
+		CloseHandle(handle);
+		return 1;
+	}
+	printf("write file succeed\n");
+	printf("Press Enter when ready for reading...");
+	getchar();
+
+	if (!ReadFile(handle, data, sizeof(data), &n, NULL)) {
+		printf("read file error %d\n", GetLastError());
+		printLastError();
+		CloseHandle(handle);
+		return 1;
+	}
+	printf("received data %d:\n", n);
+	for (i = 0; i < n; ++i) {
+		printf("%02x", data[i]);
+	}
+	printf("\n");
+
+	CloseHandle(handle);
+	printf("closed\n");
+	return 0;
+}

+ 83 - 0
SERVER/Meter_Service/core/modbus/test/commw32/commw32.go

@@ -0,0 +1,83 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license.  See the LICENSE file for details.
+//go:build windows && cgo
+// +build windows,cgo
+
+// Port of commw32.c
+// To generate go types: go tool cgo commw32.go
+package main
+
+// #include <windows.h>
+import "C"
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"syscall"
+)
+
+const port = "COM4"
+
+func main() {
+	handle, err := syscall.CreateFile(syscall.StringToUTF16Ptr(port),
+		syscall.GENERIC_READ|syscall.GENERIC_WRITE,
+		0,                     // mode
+		nil,                   // security
+		syscall.OPEN_EXISTING, // no creating new
+		0,
+		0)
+	if err != nil {
+		fmt.Print(err)
+		return
+	}
+	fmt.Printf("handle created %d\n", handle)
+	defer syscall.CloseHandle(handle)
+
+	var dcb C.DCB
+	dcb.BaudRate = 9600
+	dcb.ByteSize = 8
+	dcb.StopBits = C.ONESTOPBIT
+	dcb.Parity = C.NOPARITY
+	if C.SetCommState(C.HANDLE(handle), &dcb) == 0 {
+		fmt.Printf("set comm state error %v\n", syscall.GetLastError())
+		return
+	}
+	fmt.Printf("set comm state succeed\n")
+
+	var timeouts C.COMMTIMEOUTS
+	// time-out between charactor for receiving (ms)
+	timeouts.ReadIntervalTimeout = 1000
+	timeouts.ReadTotalTimeoutMultiplier = 0
+	timeouts.ReadTotalTimeoutConstant = 1000
+	timeouts.WriteTotalTimeoutMultiplier = 0
+	timeouts.WriteTotalTimeoutConstant = 1000
+	if C.SetCommTimeouts(C.HANDLE(handle), &timeouts) == 0 {
+		fmt.Printf("set comm timeouts error %v\n", syscall.GetLastError())
+		return
+	}
+	fmt.Printf("set comm timeouts succeed\n")
+
+	var n uint32
+	data := []byte("abc")
+	err = syscall.WriteFile(handle, data, &n, nil)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	fmt.Printf("write file succeed\n")
+	fmt.Printf("Press Enter when ready for reading...")
+	reader := bufio.NewReader(os.Stdin)
+	_, _ = reader.ReadString('\n')
+
+	data = make([]byte, 512)
+	err = syscall.ReadFile(handle, data, &n, nil)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	fmt.Printf("received data %v:\n", n)
+	fmt.Printf("%x\n", data[:n])
+	fmt.Printf("closed\n")
+}

+ 49 - 0
SERVER/Meter_Service/core/modbus/test/rtuclient_test.go

@@ -0,0 +1,49 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license.  See the LICENSE file for details.
+
+package test
+
+import (
+	"log"
+	"os"
+	"testing"
+
+	"github.com/goburrow/modbus"
+)
+
+const (
+	rtuDevice = "/dev/pts/6"
+)
+
+func TestRTUClient(t *testing.T) {
+	// Diagslave does not support broadcast id.
+	handler := modbus.NewRTUClientHandler(rtuDevice)
+	handler.SlaveId = 17
+	ClientTestAll(t, modbus.NewClient(handler))
+}
+
+func TestRTUClientAdvancedUsage(t *testing.T) {
+	handler := modbus.NewRTUClientHandler(rtuDevice)
+	handler.BaudRate = 19200
+	handler.DataBits = 8
+	handler.Parity = "E"
+	handler.StopBits = 1
+	handler.SlaveId = 11
+	handler.Logger = log.New(os.Stdout, "rtu: ", log.LstdFlags)
+	err := handler.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer handler.Close()
+
+	client := modbus.NewClient(handler)
+	results, err := client.ReadDiscreteInputs(15, 2)
+	if err != nil || results == nil {
+		t.Fatal(err, results)
+	}
+	results, err = client.ReadWriteMultipleRegisters(0, 2, 2, 2, []byte{1, 2, 3, 4})
+	if err != nil || results == nil {
+		t.Fatal(err, results)
+	}
+}

+ 46 - 0
SERVER/Meter_Service/core/modbus/test/tcpclient_test.go

@@ -0,0 +1,46 @@
+// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
+// This software may be modified and distributed under the terms
+// of the BSD license.  See the LICENSE file for details.
+
+package test
+
+import (
+	"log"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/goburrow/modbus"
+)
+
+const (
+	tcpDevice = "localhost:5020"
+)
+
+func TestTCPClient(t *testing.T) {
+	client := modbus.TCPClient(tcpDevice)
+	ClientTestAll(t, client)
+}
+
+func TestTCPClientAdvancedUsage(t *testing.T) {
+	handler := modbus.NewTCPClientHandler(tcpDevice)
+	handler.Timeout = 5 * time.Second
+	handler.SlaveId = 1
+	handler.Logger = log.New(os.Stdout, "tcp: ", log.LstdFlags)
+	handler.Connect()
+	defer handler.Close()
+
+	client := modbus.NewClient(handler)
+	results, err := client.ReadDiscreteInputs(15, 2)
+	if err != nil || results == nil {
+		t.Fatal(err, results)
+	}
+	results, err = client.WriteMultipleRegisters(1, 2, []byte{0, 3, 0, 4})
+	if err != nil || results == nil {
+		t.Fatal(err, results)
+	}
+	results, err = client.WriteMultipleCoils(5, 10, []byte{4, 3})
+	if err != nil || results == nil {
+		t.Fatal(err, results)
+	}
+}

+ 63 - 0
SERVER/Meter_Service/core/tcpserver/gontpd.go

@@ -0,0 +1,63 @@
+package tcpserver
+
+import (
+	"encoding/binary"
+	//	"flag"
+	"fmt"
+	"log"
+	"net"
+	"time"
+)
+
+const ntpEpochOffset = 2208988800
+
+type packet struct {
+	Settings       uint8
+	Stratum        uint8
+	Poll           int8
+	Precision      int8
+	RootDelay      uint32
+	RootDispersion uint32
+	ReferenceID    uint32
+	RefTimeSec     uint32
+	RefTimeFrac    uint32
+	OrigTimeSec    uint32
+	OrigTimeFrac   uint32
+	RxTimeSec      uint32
+	RxTimeFrac     uint32
+	TxTimeSec      uint32
+	TxTimeFrac     uint32
+}
+
+func GoNtpd() {
+	//var host string
+	//flag.StringVar(&host, "e", "ntp.api.bz", "NTP host") //"182.92.12.11:123"
+	//flag.Parse()
+
+	conn, err := net.Dial("udp", "ntp.api.bz:123") //host)
+	if err != nil {
+		log.Fatalf("failed to connect: %v", err)
+	}
+	defer conn.Close()
+	if err := conn.SetDeadline(time.Now().Add(15 * time.Second)); err != nil {
+		log.Fatalf("failed to set deadline: %v", err)
+	}
+
+	req := &packet{Settings: 0x1B}
+
+	if err := binary.Write(conn, binary.BigEndian, req); err != nil {
+		log.Fatalf("failed to send request: %v", err)
+	}
+
+	rsp := &packet{}
+	if err := binary.Read(conn, binary.BigEndian, rsp); err != nil {
+		log.Fatalf("failed to read server response: %v", err)
+	}
+
+	secs := float64(rsp.TxTimeSec) - ntpEpochOffset
+	nanos := (int64(rsp.TxTimeFrac) * 1e9) >> 32
+
+	showtime := time.Unix(int64(secs), nanos)
+	fmt.Printf("%v\n", showtime)
+	fmt.Println("now:", int64(secs))
+}

+ 393 - 0
SERVER/Meter_Service/core/tcpserver/tcpserver.go

@@ -0,0 +1,393 @@
+package tcpserver
+
+import (
+	"MeterService/core/logger"
+	"bufio"
+	"crypto/tls"
+	"crypto/x509"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+var (
+	chanBuffSize = 32
+)
+
+type RestResult struct {
+	Success bool   `json:"success"`
+	Message string `json:"message"`
+	Data    string `json:"data"`
+	Stream  []byte `json:"stream"`
+}
+type Client struct {
+	conn net.Conn
+	//RD_chan   chan []byte
+	//WR_chan   chan []byte
+	//Exit_chan chan error // 异常退出通道
+	//channel_rd  chan []byte
+	rwLock     sync.RWMutex
+	channelWr  chan []byte
+	channelErr chan int
+	rdChanFlag bool //读取buffRawRes通道标志位,true表示有线程等待读通道
+	gRunFlag   bool //协程允许标记
+	//towr        chan []byte
+	tord        chan []byte
+	clientId    string //客户端ID(SN)
+	clientAddr  string //客户端地址
+	buffRestRes chan RestResult
+	buffRawRes  chan []byte //原始数据
+	Server      *Server
+	restCnt     int
+}
+type Server struct {
+	address                   string
+	config                    *tls.Config
+	onNewClientCallback       func(c *Client)
+	onClientConnectionClosed  func(c *Client, err error)
+	onNewMessage              func(c *Client, msg []byte)
+	onMsgClientCallback       func(c *Client, msg []byte) bool
+	onMsgClientCallbackDefine func(c *Client, msg []byte) bool
+}
+
+// LockClient 锁定tcp通道,与解锁成对使用
+func (c *Client) LockClient() {
+	c.rwLock.Lock()
+}
+
+// UnLockClient 解锁tcp通道
+func (c *Client) UnLockClient() {
+	c.rwLock.Unlock()
+}
+func (c *Client) SetWaitRawRes() {
+	c.rdChanFlag = true
+}
+func (c *Client) ClrWaitRawRes() {
+	c.rdChanFlag = false
+}
+func (c *Client) GetWaitRawRes() bool {
+	return c.rdChanFlag
+}
+
+// PutRestRes 以json格式推入
+func (c *Client) PutRestRes(data RestResult) {
+	//sync_lock
+	//log.Println("PutRestRes:", data)
+	c.buffRestRes <- data
+}
+
+// PutRawRes 或者以原始格式推入
+func (c *Client) PutRawRes(data []byte) {
+	//sync_lock
+	//log.Printf("PutRestRes:%d\r\n", len(data))
+	c.buffRawRes <- data
+}
+
+// GetRestRes 以json格式获取
+func (c *Client) GetRestRes() (RestResult, bool) {
+	//sync_lock
+	var (
+		ret bool = false
+		res RestResult
+	)
+	select {
+	case res = <-c.buffRestRes:
+		ret = true
+		//log.Println("GetRestRes:", res)
+		//log.Printf("buffRestRes len:%d", len(c.buffRestRes))
+	case <-time.After(time.Second * 10):
+		ret = false
+	}
+	c.restCnt++
+	return res, ret
+}
+
+func (c *Client) GetRestCnt() int {
+	return c.restCnt
+}
+
+// GetRawRes 以原始格式获取
+func (c *Client) GetRawRes() ([]byte, bool) {
+	//sync_lock
+	var (
+		ret bool   = false
+		res []byte = make([]byte, 0) //256)
+	)
+	select {
+	case res = <-c.buffRawRes:
+		ret = true
+	case <-time.After(time.Second * 5):
+		ret = false
+	}
+	c.restCnt++
+	return res, ret
+}
+
+// ChanWrite 将数据写给User端
+func (c *Client) ChanWrite(b []byte) int {
+	//sync_lock
+	c.channelWr <- b
+	c.restCnt++
+	return c.restCnt
+}
+
+func (c *Client) GetClientHost() string {
+	return c.clientAddr
+}
+func (c *Client) GetClientRegisterID() string {
+	return c.clientId
+}
+func (c *Client) SetClientRegisterID(id string) {
+	c.clientId = id
+}
+
+// GetGRunFlag 协程运行标记
+func (c *Client) GetGRunFlag() bool {
+	return c.gRunFlag
+}
+
+func (c *Client) handleClient() {
+	res := ""
+	cnt := 0
+	ro := make(chan int)
+	//ctx, cancel := context.WithCancel(context.Background())
+	//wo := make(chan int)
+	//rexit := make(chan int)
+	//wexit := make(chan int)
+	go c.goRead(c.tord, ro)
+	//go c.handleTaskTimer(ro)
+	//go c.gowrite(c.towr, wo)
+	c.clientAddr = c.Conn().RemoteAddr().String()
+	c.Server.onNewClientCallback(c)
+	c.restCnt = 0
+	c.gRunFlag = true
+	defer func() {
+		c.closeClient()
+		if res != "ro" {
+			<-ro
+		}
+	}()
+	for {
+		select {
+		case <-time.After(time.Minute * 2):
+			cnt++
+			if cnt > 2 {
+				res = "over time"
+				return //exit = true
+			}
+		case <-ro:
+			res = "ro"
+			return //exit = true
+		case <-c.channelErr:
+			res = "channel err"
+			return //exit = true
+		case wrData := <-c.channelWr:
+			if _, err := c.conn.Write(wrData); err != nil { // && err != io.EOF {
+				res = "wo"
+				return //exit = true
+			}
+		case rData, ok := <-c.tord:
+			if ok {
+				cnt = 0
+				c.Server.onNewMessage(c, rData)
+			}
+		}
+	}
+}
+
+func (c *Client) closeClient() {
+	var err error
+	defer func(conn net.Conn) {
+		_ = conn.Close()
+	}(c.conn)
+	c.gRunFlag = false
+	c.Server.onClientConnectionClosed(c, err)
+}
+
+func (c *Client) ExitClient() {
+	c.channelErr <- 0 //errors.New("exit")
+}
+
+func (c *Client) goRead(buff chan<- []byte, out chan<- int) {
+	size := (int)(32 * 1024)
+	data := make([]byte, size)
+	reader := bufio.NewReader(c.conn)
+	for {
+		n, err := reader.Read(data) //c.conn.Read(data) //
+		if err != nil {
+			close(buff)
+			out <- 0
+			return
+		} else {
+			if n > 0 && n < size-1 {
+				buff <- data[:n]
+			}
+		}
+	}
+}
+
+func SendTo(cli *Client, date []byte) (res []byte, ok bool) {
+	var (
+		en = false
+	)
+	ok = true
+	cli.LockClient()
+	cnt := cli.ChanWrite(date)
+	select {
+	case <-time.After(time.Millisecond * 800):
+	}
+	for {
+		res, ok = cli.GetRawRes()
+		nCnt := cli.GetRestCnt()
+		if !ok {
+			//log.Println("Over Time...")
+			break
+		}
+		if nCnt == cnt+1 {
+			break
+		}
+		if nCnt > cnt+1 {
+			ok = false
+			break
+		}
+		if en {
+			break
+		}
+		en = true
+	}
+	cli.UnLockClient()
+	return
+}
+
+func (c *Client) Send(msg string) error {
+	_, err := c.conn.Write([]byte(msg))
+	return err
+}
+func (c *Client) SendBytes(msg []byte) error {
+	_, err := c.conn.Write(msg)
+	return err
+}
+func (c *Client) Conn() net.Conn {
+	return c.conn
+}
+func (c *Client) Close() error {
+	return c.conn.Close()
+}
+
+func (s *Server) OnNewClient(callback func(c *Client)) {
+	s.onNewClientCallback = callback
+}
+func (s *Server) OnClientConnectionClosed(callback func(c *Client, err error)) {
+	s.onClientConnectionClosed = callback
+}
+func (s *Server) OnNewMessage(callback func(c *Client, msg []byte)) {
+	s.onNewMessage = callback
+}
+
+func (s *Server) OnMsgClientCallbackDefine(callback func(c *Client, msg []byte) bool) {
+	s.onMsgClientCallbackDefine = callback
+}
+func (s *Server) OnMsgClientCallback(callback func(c *Client, msg []byte) bool) {
+	s.onMsgClientCallback = callback
+}
+
+func (s *Server) Listen() {
+	var listener net.Listener
+	var err error
+	if s.config == nil {
+		listener, err = net.Listen("tcp", s.address)
+	} else {
+		listener, err = tls.Listen("tcp", s.address, s.config)
+	}
+	if err != nil {
+		log.Fatal("Error starting TCP server")
+	}
+	defer func(listener net.Listener) {
+		_ = listener.Close()
+	}(listener)
+	for {
+		conn, _ := listener.Accept()
+		if tcpConn, ok := conn.(*net.TCPConn); ok {
+			if err := tcpConn.SetKeepAlive(false); err != nil {
+				//fmt.Println("close keepalive fail")
+			} else {
+				//fmt.Println("close keepalive ok")
+			}
+		}
+		Client := &Client{
+			conn:   conn,
+			Server: s,
+			//towr:       make(chan []byte),
+			tord:      make(chan []byte),
+			channelWr: make(chan []byte, chanBuffSize),
+			//channel_rd:  make(chan []byte, chanBuffSize),
+			channelErr:  make(chan int),
+			buffRestRes: make(chan RestResult, chanBuffSize),
+			buffRawRes:  make(chan []byte, chanBuffSize),
+			clientId:    "",
+			clientAddr:  "",
+			rdChanFlag:  false,
+			gRunFlag:    false,
+			//reConn: make(chan bool),
+		}
+		go Client.handleClient()
+		//go Client.listen()
+	}
+}
+
+func New(address string) *Server {
+	logger.Info("创建TCP服务,端口 [%s]", address)
+	server := &Server{
+		address: address,
+		config:  nil,
+	}
+	server.OnNewClient(func(c *Client) {})
+	server.OnNewMessage(func(c *Client, msg []byte) {})
+	server.OnClientConnectionClosed(func(c *Client, err error) {})
+	return server
+}
+
+func NewWithTLS(address, certFile, keyFile string) *Server {
+	logger.Info("创建TCP服务,端口 [%s]", address)
+	conf, err := serverTLSConf(certFile, keyFile)
+	// sconf.ClientAuth = tls.RequireAndVerifyClientCert
+	if err != nil {
+		fmt.Println("创建TCP服务失败", err)
+		return nil
+	}
+	//conf.MaxVersion = tls.VersionTLS12
+	conf.BuildNameToCertificate()
+	server := &Server{
+		address: address,
+		config:  conf,
+	}
+	server.OnNewClient(func(c *Client) {})
+	server.OnNewMessage(func(c *Client, msg []byte) {})
+	server.OnClientConnectionClosed(func(c *Client, err error) {})
+	return server
+}
+
+func serverTLSConf(certFile, keyFile string) (*tls.Config, error) {
+	cacert, _ := os.ReadFile("./cert/ca.crt")
+	pool := x509.NewCertPool()
+	pool.AppendCertsFromPEM(cacert)
+
+	tlsConf := new(tls.Config)
+	//tlsConf.PreferServerCipherSuites = true
+	tlsConf.ClientCAs = pool
+	tlsConf.ClientAuth = tls.NoClientCert //RequireAndVerifyClientCert
+	// support http2
+	//tlsConf.NextProtos = append(tlsConf.NextProtos, "h2", "http/1.1")
+	// 准备证书
+	tlsConf.Certificates = make([]tls.Certificate, 1)
+	var err error
+	tlsConf.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+	if err != nil {
+		return nil, err
+	}
+	// tlsConf.KeyLogWriter = handsh.KeyLog("server")
+	return tlsConf, nil
+}

+ 78 - 0
SERVER/Meter_Service/core/utils/httpHelper/HTTPClientHelper.go

@@ -0,0 +1,78 @@
+package httpHelper
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+
+	"github.com/pkg/errors"
+)
+
+// HTTPClientHelper 提供了对HTTP API调用的基本封装
+type HTTPClientHelper struct {
+	client *http.Client
+}
+
+// NewHTTPClientHelper 创建一个新的HTTPClientHelper实例,并使用默认的http.Client
+func NewHTTPClientHelper() *HTTPClientHelper {
+	return &HTTPClientHelper{
+		client: &http.Client{},
+	}
+}
+
+// Get 发起一个GET请求并返回响应体
+func (h *HTTPClientHelper) Get(url string) ([]byte, error) {
+	resp, err := h.client.Get(url)
+	if err != nil {
+		return nil, errors.Wrap(err, "发起GET请求时出错")
+	}
+	defer func(Body io.ReadCloser) {
+		_ = Body.Close()
+	}(resp.Body)
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, errors.Wrap(err, "读取响应体时出错")
+	}
+
+	if resp.StatusCode >= 400 {
+		return nil, fmt.Errorf("收到非成功状态码: %d", resp.StatusCode)
+	}
+
+	return body, nil
+}
+
+// PostJSON 发起一个POST请求,发送JSON格式的数据,并返回响应体
+func (h *HTTPClientHelper) PostJSON(url string, payload interface{}) ([]byte, error) {
+	jsonPayload, err := json.Marshal(payload)
+	if err != nil {
+		return nil, errors.Wrap(err, "序列化JSON数据时出错")
+	}
+
+	req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonPayload))
+	if err != nil {
+		return nil, errors.Wrap(err, "创建POST请求时出错")
+	}
+	req.Header.Set("Content-Type", "application/json")
+
+	resp, err := h.client.Do(req)
+	if err != nil {
+		return nil, errors.Wrap(err, "执行POST请求时出错")
+	}
+	defer func(Body io.ReadCloser) {
+		_ = Body.Close()
+	}(resp.Body)
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, errors.Wrap(err, "读取POST响应体时出错")
+	}
+
+	if resp.StatusCode >= 400 {
+		return nil, fmt.Errorf("收到非成功状态码: %d", resp.StatusCode)
+	}
+
+	return body, nil
+}

+ 112 - 0
SERVER/Meter_Service/core/utils/security.go

@@ -0,0 +1,112 @@
+package utils
+
+import (
+	"crypto/rand"
+	"encoding/hex"
+	"errors"
+	"log"
+
+	"golang.org/x/crypto/scrypt"
+)
+
+const (
+	SymbolSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+,.?/:;{}[]`~"
+	LetterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+	NumberSet = "0123456789"
+)
+
+// ErrInvalidCharsetLength 表示字符集长度错误
+var ErrInvalidCharsetLength = errors.New("字符集长度错误")
+
+func generateRandString(length int, charset string) (string, error) {
+	var chars = []byte(charset)
+	clen := len(chars)
+	if clen < 2 || clen > 256 {
+		return "", ErrInvalidCharsetLength
+	}
+	maxRb := 255 - (256 % clen)
+	b := make([]byte, length)
+	r := make([]byte, length+(length/4)) // storage for random bytes.
+	i := 0
+	for {
+		n, err := rand.Read(r)
+		if err != nil {
+			log.Printf("读取随机字节出错: %v", err)
+			return "", err
+		}
+		if n != len(r) {
+			log.Printf("未预期的随机字节数量: %d", n)
+			return "", errors.New("随机字节长度不正确")
+		}
+		for _, rb := range r {
+			c := int(rb)
+			if c > maxRb {
+				continue // Skip this number to avoid modulo bias.
+			}
+			b[i] = chars[c%clen]
+			i++
+			if i == length {
+				return string(b), nil
+			}
+		}
+	}
+}
+
+// GenerateRandomString 生成指定长度的随机字符串
+func GenerateRandomString(length int, charset string) string {
+	str, err := generateRandString(length, charset)
+	if err != nil {
+		log.Printf("生成随机字符串失败: %v", err)
+		return ""
+	}
+	return str
+}
+
+// GenerateRandomKey 生成指定长度的随机字符串,使用预定义的字符集
+func GenerateRandomKey(length int, charsetType int) string {
+	var charset string
+	switch charsetType {
+	case 1: // Symbol set
+		charset = SymbolSet
+	case 2: // Letter set
+		charset = LetterSet
+	case 3: // Number set
+		charset = NumberSet
+	default:
+		log.Println("无效的字符集类型")
+		return ""
+	}
+	return GenerateRandomString(length, charset)
+}
+
+// GenerateRandomKey20 生成20位随机字符串
+func GenerateRandomKey20() string {
+	return GenerateRandomKey(20, 1)
+}
+
+// GenerateRandomKey16 生成16位随机字符串
+func GenerateRandomKey16() string {
+	return GenerateRandomKey(16, 1)
+}
+
+// GenerateRandomKey6 生成6位随机字符串
+func GenerateRandomKey6() string {
+	return GenerateRandomKey(6, 2)
+}
+
+// GenerateRandomKey4 生成4位随机字符串
+func GenerateRandomKey4() string {
+	return GenerateRandomKey(4, 3)
+}
+
+// SetPassword 根据明文密码和加盐值生成密码
+func SetPassword(password string, salt string) (verify string, err error) {
+	var rb []byte
+	rb, err = scrypt.Key([]byte(password), []byte(salt), 16384, 8, 1, 32)
+	if err != nil {
+		log.Printf("生成密码哈希失败: %v", err)
+		return
+	}
+	verify = hex.EncodeToString(rb)
+	return
+}

+ 63 - 0
SERVER/Meter_Service/core/utils/string.go

@@ -0,0 +1,63 @@
+package utils
+
+import (
+	"fmt"
+	"reflect"
+	"strconv"
+)
+
+// HasPrefix 判断字符串是否以指定前缀开头
+func HasPrefix(s *string, prefix string) bool {
+	pl := len(prefix)
+	return len(*s) >= pl && (*s)[:pl] == prefix
+}
+
+// IsPrefix 检查字符串 s 是否以 prefix 开头,并返回一个新的字符串,
+// 如果 s 不以 prefix 开头,则在 s 前面添加 prefix。
+func IsPrefix(s, prefix string) string {
+	if s == "" || prefix == "" {
+		return s + prefix
+	}
+	if HasPrefix(&s, prefix) {
+		return s
+	}
+	return prefix + s
+}
+
+// HasSuffix 判断字符串是否以指定后缀结尾
+func HasSuffix(s *string, suffix string) bool {
+	if s == nil {
+		return false
+	}
+	return len(*s) >= len(suffix) && (*s)[len(*s)-len(suffix):] == suffix
+}
+
+// IsSuffix 检查字符串 s 是否以 suffix 结尾,并返回一个新的字符串,
+// 如果 s 不以 suffix 结尾,则在 s 后面添加 suffix。
+func IsSuffix(s, suffix string) string {
+	if s == "" || suffix == "" {
+		return s + suffix
+	}
+	if HasSuffix(&s, suffix) {
+		return s
+	}
+	return s + suffix
+}
+
+func ToString(i interface{}) (string, error) {
+	value := reflect.ValueOf(i)
+	switch value.Kind() {
+	case reflect.String:
+		return value.String(), nil
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return strconv.FormatInt(value.Int(), 10), nil
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return strconv.FormatUint(value.Uint(), 10), nil
+	case reflect.Float32, reflect.Float64:
+		return strconv.FormatFloat(value.Float(), 'g', -1, 64), nil
+	case reflect.Bool:
+		return strconv.FormatBool(value.Bool()), nil
+	default:
+		return "", fmt.Errorf("无法将 %s 类型转换为 string", value.Type())
+	}
+}

+ 56 - 0
SERVER/Meter_Service/core/utils/util.go

@@ -0,0 +1,56 @@
+package utils
+
+import (
+	"MeterService/core/constant"
+	"time"
+)
+
+// IsBigEnd 判断是否大端
+func IsBigEnd() bool {
+	var (
+		v3 uint32
+		b3 [4]byte
+	)
+	v3 = 257
+	b3[0] = uint8(v3)
+	b3[1] = uint8(v3 >> 8)
+	b3[2] = uint8(v3 >> 16)
+	b3[3] = uint8(v3 >> 24)
+	if b3[0] == 1 {
+		return false
+	}
+	return true
+}
+
+func IntoByte(c int) byte {
+	var (
+		b3 [4]byte
+	)
+	b3[0] = uint8(c)
+	b3[1] = uint8(c >> 8)
+	b3[2] = uint8(c >> 16)
+	b3[3] = uint8(c >> 24)
+	if IsBigEnd() {
+		return b3[3]
+	}
+	return b3[0]
+}
+
+// NowDataToTimeStamp 获取当前时间戳
+func NowDataToTimeStamp() int64 {
+	v := time.Now()
+	return time.Date(v.Year(), v.Month(), v.Day(), v.Hour(), v.Minute(), 0, 0, time.Local).Unix()
+}
+
+func ConnTimeoutJudge(tm int64) bool {
+	return TimeDiff(tm, int64(constant.TCPTimeoutTime))
+}
+
+// TimeDiff 时间差计算,true表示超过时间
+func TimeDiff(tm, mat int64) bool {
+	if time.Now().Unix()-tm > mat {
+		return true
+	} else {
+		return false
+	}
+}

+ 62 - 0
SERVER/Meter_Service/data/device.go

@@ -0,0 +1,62 @@
+package data
+
+import (
+	"MeterService/core/logger"
+	"MeterService/core/utils"
+	"MeterService/core/utils/httpHelper"
+	"MeterService/dataStruct"
+	"MeterService/database/appApi"
+	"encoding/json"
+	"errors"
+)
+
+func initDevice() error {
+	appApiMap := dataStruct.NewMapAppApi()
+	apiMap, ok := appApiMap.Get(dataStruct.GetDevices)
+	if !ok {
+		logger.Error("获取 设备加载API 错误")
+		return errors.New("获取 设备加载API 错误")
+	}
+	for k, v := range apiMap {
+		go loadDevice(k, v)
+	}
+	return nil
+}
+
+func loadDevice(appId int, api *appApi.AppApi) {
+	if api == nil {
+		logger.Error("应用[%d] 获取 设备加载API 错误", appId)
+		return
+	}
+	http := httpHelper.NewHTTPClientHelper()
+	url := api.Url
+	if !utils.HasPrefix(&url, "http") {
+		url = utils.IsPrefix(url, "/")
+		url = api.Host + url
+	}
+	res, err := http.Get(url)
+	if err != nil {
+		logger.Error("应用[%d] 获取 设备加载API 错误", appId)
+		return
+	}
+
+	devices := &[]dataStruct.DtuDevice{}
+	err = json.Unmarshal(res, devices)
+	if err != nil {
+		return
+	}
+	for _, device := range *devices {
+		dtu, err := device.ToDtu()
+		if err != nil {
+			return
+		}
+
+		dtuState := &dataStruct.DTUDeviceState{
+			Info:   dtu,
+			Online: false,
+			PwrOff: false,
+		}
+		DtuMap.Add(dtu.SN, dtuState)
+		logger.Debug("应用[%d] 加载设备 [ %s ]", appId, dtu.SN)
+	}
+}

+ 85 - 0
SERVER/Meter_Service/data/dtu.go

@@ -0,0 +1,85 @@
+package data
+
+import (
+	"MeterService/dataStruct"
+	"MeterService/database/appApi"
+	"MeterService/database/meterCalcParam"
+)
+
+var (
+	DtuMap      *dataStruct.DMapDtuDev
+	CalcParam   *dataStruct.DMapInterface
+	DtuMapState *dataStruct.DMapClientState
+	IpSN        *dataStruct.DMapKv
+	OnlineSN    *dataStruct.DMapClientState
+	TranSN      chan *dataStruct.DtuRegisterChanMsg
+	AppApiMap   *dataStruct.MapAppApi
+)
+
+func InitData() bool {
+	OnlineSN = dataStruct.NewDMapClientState()
+	IpSN = dataStruct.NewDMapKv()
+	TranSN = make(chan *dataStruct.DtuRegisterChanMsg, 100)
+	DtuMapState = dataStruct.NewDMapClientState()
+	DtuMap = dataStruct.NewDMapDtuDev()
+	CalcParam = initMeterCalcParam()
+	initAppApiMap()
+	// 初始化DtuMap
+	if err := initDevice(); err != nil {
+		return false
+	}
+	return true
+}
+
+func initMeterCalcParam() *dataStruct.DMapInterface {
+	calcParam := dataStruct.NewDMapInterface()
+	db := meterCalcParam.NewMeterCalcParamDb()
+	if db == nil {
+		return calcParam
+	}
+	if array, err := db.GetList(); err == nil {
+		for _, v := range array {
+			val := v.(*meterCalcParam.MeterCalcParam)
+			calc := &dataStruct.MeterCalcParam{
+				Id:   val.Id,
+				Time: val.Time,
+				PowerRate: dataStruct.CalcPowerRate{
+					MaxPower: val.MaxPower,
+					Count:    val.Count,
+					SumPower: val.SumPower,
+				},
+				TotalEnergy: dataStruct.CalcDataEnergy{
+					Tp: val.Tps,
+					Tq: val.Tqs,
+					Fp: val.Fps,
+					Fq: val.Fqs,
+				},
+				DayTotalEnergy: dataStruct.CalcDataEnergy{
+					Tp: val.Tpe,
+					Tq: val.Tqe,
+					Fp: val.Fpe,
+					Fq: val.Fqe,
+				},
+			}
+			calcParam.Add(calc.Id, calc)
+		}
+	}
+	return calcParam
+}
+
+func initAppApiMap() *dataStruct.MapAppApi {
+	appApiMap := dataStruct.NewMapAppApi()
+	db := appApi.NewAppApiDb()
+	if db == nil {
+		return appApiMap
+	}
+	if array, err := db.GetList(); err == nil {
+		for _, v := range array {
+			api := v.(*appApi.AppApi)
+			if appApiMap.IsAppApiExist(api.Type) {
+				appApiMap.Add(api.Type, api)
+			}
+		}
+	}
+	return appApiMap
+}

+ 13 - 0
SERVER/Meter_Service/dataStruct/clientState.go

@@ -0,0 +1,13 @@
+package dataStruct
+
+import "MeterService/core/tcpserver"
+
+type ClientState struct {
+	FD     *tcpserver.Client
+	Times  int64 //心跳计时器
+	Online bool  //在线状态
+	Config *DtuConfig
+	// PwrOff    bool  //掉电状态
+	TmOnline  string
+	TmOffline string
+}

+ 104 - 0
SERVER/Meter_Service/dataStruct/collect.go

@@ -0,0 +1,104 @@
+package dataStruct
+
+type CollectData struct {
+	Slave    *DtuSlave
+	MeterRef *MeterRef //电表参数
+	PT       int       //电压变比
+	CT       int       //电流变比
+	//电表参数
+	P            float32
+	Pa           float32
+	Pb           float32
+	Pc           float32
+	Q            float32
+	Qa           float32
+	Qb           float32
+	Qc           float32
+	Pf           float32
+	Pfa          float32
+	Pfb          float32
+	Pfc          float32
+	Ia           float32
+	Ib           float32
+	Ic           float32
+	Iz           float32
+	Ua           float32
+	Ub           float32
+	Uc           float32
+	Uab          float32
+	Ubc          float32
+	Uca          float32
+	Uaw          float32
+	Ubw          float32
+	Ucw          float32
+	Uabw         float32
+	Ubcw         float32
+	Ucaw         float32
+	Pv           float32
+	Freq         float32
+	Fw           float32
+	Dp           float32 //需量负荷
+	IUnbalance   float32 //电流不平衡度
+	UUnbalance   float32 //电压不平衡度
+	TemperatureA float32
+	TemperatureB float32
+	TemperatureC float32
+	TemperatureZ float32
+	Tps          float32
+	Tqs          float32
+	Fps          float32
+	Fqs          float32
+	Tpe          float32
+	Tqe          float32
+	Fpe          float32
+	Fqe          float32
+	BaseIa       float32
+	BaseIb       float32
+	BaseIc       float32
+	BaseUa       float32
+	BaseUb       float32
+	BaseUc       float32
+	HarIa        float32
+	HarIb        float32
+	HarIc        float32
+	HarUa        float32
+	HarUb        float32
+	HarUc        float32
+	IaHar        float32
+	IbHar        float32
+	IcHar        float32
+	UaHar        float32
+	UbHar        float32
+	UcHar        float32
+	Hia          [15]float32 //A相3-31奇次电流谐波
+	Hib          [15]float32 //B相3-31奇次电流谐波
+	Hic          [15]float32 //C相3-31奇次电流谐波
+	Hua          [15]float32 //A相3-31奇次电压谐波
+	Hub          [15]float32 //B相3-31奇次电压谐波
+	Huc          [15]float32 //C相3-31奇次电压谐波
+
+}
+
+// CalcDiff 计算偏差
+func (d *CollectData) CalcDiff() {
+	ref := d.MeterRef
+	d.Fw = d.Freq - float32(50.0)
+	d.Uaw = (d.Ua - ref.LvRef) * float32(100.0) / ref.LvRef
+	d.Ubw = (d.Ub - ref.LvRef) * float32(100.0) / ref.LvRef
+	d.Ucw = (d.Uc - ref.LvRef) * float32(100.0) / ref.LvRef
+	d.Uabw = (d.Uab - ref.PvRef) * float32(100.0) / ref.PvRef
+	d.Ubcw = (d.Ubc - ref.PvRef) * float32(100.0) / ref.PvRef
+	d.Ucaw = (d.Uca - ref.PvRef) * float32(100.0) / ref.PvRef
+}
+
+// CalcWithParam 计算电表部分数据
+func (d *CollectData) CalcWithParam(m *MeterCalcParam) {
+	m.CalcAndSetPowerRate(d)
+	m.CalcAndSetDayEnergy(d)
+	m.SetTotalEnergy(d)
+	d.Tpe = m.DayTotalEnergy.Tp
+	d.Tqe = m.DayTotalEnergy.Tq
+	d.Fpe = m.DayTotalEnergy.Fp
+	d.Fqe = m.DayTotalEnergy.Fq
+	d.Pv = m.CalcRate()
+}

+ 110 - 0
SERVER/Meter_Service/dataStruct/dtuConfig.go

@@ -0,0 +1,110 @@
+package dataStruct
+
+import "strings"
+
+//type DtuConfig struct {
+//	Platform []DtuConfigItem `json:"platform"`
+//}
+
+type DtuConfig struct {
+	Enable   bool   `json:"enable"`
+	ID       int    `json:"id"`
+	Name     string `json:"name"`
+	IP       string `json:"ip"`
+	Port     int    `json:"port"`
+	Protocol string `json:"protocol"`
+	Pw       string `json:"pw"`
+	Mn       string `json:"mn"`
+	Secs     int    `json:"secs"`
+	St       string `json:"st"`
+	Cn       string `json:"cn"`
+	Others   string `json:"others"`
+
+	Slave []*DtuSlave `json:"slave"`
+}
+
+type DtuSlave struct {
+	Addr  int               `json:"addr"`  //设备串口地址
+	NO    string            `json:"no"`    //设备编号
+	LvRef float32           `json:"lvRef"` //线电压基准(220)
+	PvRef float32           `json:"pvRef"` //相电压基准(380)
+	MType string            `json:"mType"` //设备类型
+	BmYz  map[string]string `json:"bmYz"`  //编码因子
+}
+
+type BmYz struct {
+	P          string
+	Pa         string
+	Pb         string
+	Pc         string
+	Q          string
+	Qa         string
+	Qb         string
+	Qc         string
+	Pf         string
+	Pfa        string
+	Pfb        string
+	Pfc        string
+	Ua         string
+	Ub         string
+	Uc         string
+	Uab        string
+	Ubc        string
+	Uca        string
+	Ia         string
+	Ib         string
+	Ic         string
+	Iz         string
+	Uaw        string
+	Ubw        string
+	Ucw        string
+	Uabw       string
+	Ubcw       string
+	Ucaw       string
+	F          string
+	FW         string
+	IUnbalance string
+	UUnbalance string
+	Dp         string
+	Pv         string
+	Tpe        string
+	Tqe        string
+	Fpe        string
+	Fqe        string
+	Tps        string
+	Tqs        string
+	Fps        string
+	Fqs        string
+	T          string
+	T2         string
+	T3         string
+	T4         string
+	T5         string
+	T6         string
+	T7         string
+}
+
+// GetBmYzKey 获取BmYz的键
+func (ds *DtuSlave) GetBmYzKey(key string) string {
+	if ds.BmYz == nil {
+		return strings.ToLower(key)
+
+	}
+	value, ok := ds.BmYz[key]
+	if ok {
+		return value
+	}
+	return strings.ToLower(key)
+}
+
+// GetBmYzKeyOrDefault 获取BmYz的键
+func (ds *DtuSlave) GetBmYzKeyOrDefault(key string, defaultValue string) string {
+	if ds.BmYz == nil {
+		return defaultValue
+	}
+	value, ok := ds.BmYz[key]
+	if ok {
+		return value
+	}
+	return defaultValue
+}

+ 88 - 0
SERVER/Meter_Service/dataStruct/dtuDevice.go

@@ -0,0 +1,88 @@
+package dataStruct
+
+import "encoding/json"
+
+type DTUDevice struct {
+	SN        string `json:"sn"`
+	Name      string `json:"name"`
+	SimId     string `json:"simId"`
+	Configs   string `json:"configs"`
+	TmOnline  string `json:"tmOnline"`
+	TmOffline string `json:"tmOffline"`
+}
+
+type DTUDeviceState struct {
+	Info   *DTUDevice `json:"info"`
+	Online bool       `json:"online"` //true在线
+	PwrOff bool       `json:"pwrOff"` //true掉电
+}
+
+type DtuDevice struct {
+	ID       int        `json:"id"`       // 设备ID
+	Enable   bool       `json:"enable"`   // 是否启用
+	SN       string     `json:"sn"`       // 设备编码SN
+	Name     string     `json:"name"`     // 设备名称
+	SimId    string     `json:"simId"`    // sim卡ID
+	IP       string     `json:"ip"`       // 平台IP
+	Port     int        `json:"port"`     // 平台端口
+	Protocol string     `json:"protocol"` // 平台协议
+	Pw       string     `json:"pw"`       // 平台密码
+	Mn       string     `json:"mn"`       // 设备MN
+	Secs     int        `json:"secs"`     // 上报周期
+	St       string     `json:"st"`       // 设备ST
+	Cn       string     `json:"cn"`       // 设备CN
+	Others   string     `json:"others"`   // 其他
+	Slave    []DtuSlave `json:"slave"`    // 从机
+}
+
+func (d *DtuDevice) ToDtu() (*DTUDevice, error) {
+	var dtuSlave []*DtuSlave
+	for _, slave := range d.Slave {
+		dtuSlave = append(dtuSlave, &DtuSlave{
+			Addr:  slave.Addr,
+			BmYz:  slave.BmYz,
+			LvRef: slave.LvRef,
+			MType: slave.MType,
+			PvRef: slave.PvRef,
+			NO:    slave.NO,
+		})
+	}
+	dtuConfig := &DtuConfig{
+		Enable:   d.Enable,
+		ID:       d.ID,
+		IP:       d.IP,
+		Secs:     d.Secs,
+		St:       d.St,
+		Cn:       d.Cn,
+		Others:   d.Others,
+		Port:     d.Port,
+		Protocol: d.Protocol,
+		Pw:       d.Pw,
+		Mn:       d.Mn,
+		Slave:    dtuSlave,
+	}
+	dtuConfigStr, err := json.Marshal(dtuConfig)
+	if err != nil {
+		return nil, err
+	}
+	dtuDevice := &DTUDevice{
+		Configs: string(dtuConfigStr),
+		Name:    d.Name,
+		SimId:   d.SimId,
+		SN:      d.SN,
+	}
+	return dtuDevice, nil
+}
+
+type HJBaseInfo struct {
+	//	Name     string
+	//	Protocol string
+	Host string
+	Port string
+	ST   string
+	CN   string
+	PW   string
+	MN   string
+	//	Interval int  //上报周期,单位分钟,最小1分钟
+	//	IsUpload bool //是否允许上报
+}

+ 47 - 0
SERVER/Meter_Service/dataStruct/map.go

@@ -0,0 +1,47 @@
+package dataStruct
+
+import "sync"
+
+type DMapKv struct {
+	RWLock sync.RWMutex
+	Map    map[interface{}]interface{}
+}
+
+func NewDMapKv() *DMapKv {
+	cm := &DMapKv{
+		Map: make(map[interface{}]interface{}),
+	}
+	return cm
+}
+
+func (cm *DMapKv) Add(key interface{}, val interface{}) {
+	cm.RWLock.Lock()
+	cm.Map[key] = val
+	cm.RWLock.Unlock()
+}
+
+func (cm *DMapKv) Remove(key interface{}) {
+	cm.RWLock.Lock()
+	delete(cm.Map, key)
+	cm.RWLock.Unlock()
+}
+
+func (cm *DMapKv) Get(key interface{}) (interface{}, bool) {
+	cm.RWLock.RLock()
+	res, ok := cm.Map[key]
+	cm.RWLock.RUnlock()
+	return res, ok
+}
+
+func (cm *DMapKv) Len() int {
+	return len(cm.Map)
+}
+
+func (cm *DMapKv) Clean() {
+	cm.RWLock.Lock()
+	//cm = NewDmap()
+	for key, _ := range cm.Map {
+		delete(cm.Map, key)
+	}
+	cm.RWLock.Unlock()
+}

+ 105 - 0
SERVER/Meter_Service/dataStruct/mapAppApi.go

@@ -0,0 +1,105 @@
+package dataStruct
+
+import (
+	"MeterService/database/appApi"
+	"strings"
+	"sync"
+)
+
+type MapAppApi struct {
+	RWLock  sync.Mutex //sync.RWMutex
+	Map     *sync.Map
+	TypeMap *sync.Map
+}
+
+var (
+	once      sync.Once
+	appApiMap *MapAppApi
+)
+
+const (
+	GetDevices = "GetDevices"
+	SendCmd    = "SendCmd"
+)
+
+func NewMapAppApi() *MapAppApi {
+	once.Do(func() {
+		appApiMap = &MapAppApi{
+			Map:     &sync.Map{},
+			TypeMap: &sync.Map{},
+		}
+		appApiMap.TypeMap.Store(strings.ToLower(GetDevices), "")
+		appApiMap.TypeMap.Store(strings.ToLower(SendCmd), "")
+	})
+	return appApiMap
+}
+
+func (m *MapAppApi) GetAppApiType() *sync.Map {
+	return m.TypeMap
+}
+
+func (m *MapAppApi) GetAppApiTypeList() []string {
+	array := make([]string, 0)
+	m.TypeMap.Range(func(key, value interface{}) bool {
+		array = append(array, key.(string))
+		return true
+	})
+	return array
+}
+
+func (m *MapAppApi) IsAppApiExist(apiType string) bool {
+	_, ok := m.TypeMap.Load(strings.ToLower(apiType))
+	return ok
+}
+
+func (m *MapAppApi) Add(key string, val *appApi.AppApi) {
+	m.RWLock.Lock()
+	defer m.RWLock.Unlock()
+	appMap, ok := m.Map.Load(strings.ToLower(key))
+	if !ok {
+		appMap = map[int]*appApi.AppApi{}
+	}
+	appMap.(map[int]*appApi.AppApi)[val.AppId] = val
+	m.Map.Store(strings.ToLower(key), appMap)
+}
+
+func (m *MapAppApi) Get(key string) (map[int]*appApi.AppApi, bool) {
+	m.RWLock.Lock()
+	defer m.RWLock.Unlock()
+	val, ok := m.Map.Load(strings.ToLower(key))
+	if ok {
+		return val.(map[int]*appApi.AppApi), true
+	}
+	return map[int]*appApi.AppApi{}, false
+}
+
+func (m *MapAppApi) RemoveAppId(appId int) {
+	m.RWLock.Lock()
+	defer m.RWLock.Unlock()
+	m.Map.Range(func(key, value interface{}) bool {
+		apiMap := value.(map[int]*appApi.AppApi)
+		if apiMap[appId] != nil {
+			delete(apiMap, appId)
+		}
+		return true
+	})
+}
+
+func (m *MapAppApi) Remove(key string, val *appApi.AppApi) {
+	m.RWLock.Lock()
+	defer m.RWLock.Unlock()
+	value, ok := m.Map.Load(strings.ToLower(key))
+	if ok {
+		apiMap := value.(map[int]*appApi.AppApi)
+		if apiMap[val.AppId] != nil {
+			delete(apiMap, val.AppId)
+		}
+	}
+	m.Map.Store(key, value)
+}
+
+func (m *MapAppApi) RemoveAll(key string) {
+	m.RWLock.Lock()
+	defer m.RWLock.Unlock()
+	m.Map.Delete(strings.ToLower(key))
+}

+ 103 - 0
SERVER/Meter_Service/dataStruct/mapClientState.go

@@ -0,0 +1,103 @@
+package dataStruct
+
+import (
+	"MeterService/core/utils"
+	"fmt"
+	"sync"
+	"time"
+)
+
+type DMapClientState struct {
+	RWLock sync.Mutex //sync.RWMutex
+	Map    *sync.Map
+}
+
+func NewDMapClientState() *DMapClientState {
+	cm := &DMapClientState{
+		Map: new(sync.Map),
+	}
+	return cm
+}
+
+func (cm *DMapClientState) Add(key string, val *ClientState) {
+	cm.Map.Store(key, val)
+}
+
+func (cm *DMapClientState) Remove(key string) {
+	cm.Map.Delete(key)
+}
+
+func (cm *DMapClientState) Get(key string) (*ClientState, bool) {
+	res := &ClientState{
+		Online: false,
+	}
+	x, ok := cm.Map.Load(key)
+	if ok {
+		res = x.(*ClientState)
+		res.Online = true
+		return res, true
+	}
+	return res, false
+}
+
+// AlterKey 修改key
+func (cm *DMapClientState) AlterKey(before, after string) bool {
+	if before == after {
+		return true
+	}
+	if v, ok := cm.Map.Load(before); !ok {
+		return false
+	} else {
+		if _, ok = cm.Map.Load(after); ok {
+			return false
+		}
+		val := v.(*ClientState)
+		cm.Map.Store(after, val)
+		cm.Map.Delete(before)
+		return true
+	}
+}
+
+func (cm *DMapClientState) Len() int {
+	return 0
+}
+
+func (cm *DMapClientState) Clean() {
+	//cm.Map.Range(func(key, value any) bool {
+	//	cm.Map.Delete(key)
+	//	return true
+	//})
+	cm.Map = new(sync.Map)
+}
+
+// UpdateTime 更新时间
+func (cm *ClientState) UpdateTime() {
+	cm.Times = time.Now().Unix()
+}
+
+/* ================================================== */
+
+// HeartBeat 心跳检测
+// 每遍历一次 查看所有sess 上次接收消息时间  如果超过 num 就删除该 sess
+func (cm *DMapClientState) HeartBeat(num int64) {
+	for {
+		time.Sleep(time.Second * 120)
+		cm.Map.Range(func(k, v interface{}) bool {
+			x := v.(*ClientState)
+			if x.Online {
+				if utils.ConnTimeoutJudge(x.Times) && x.FD.GetGRunFlag() {
+					fmt.Println("HeartBeat Close...")
+					x.FD.ExitClient()
+				}
+			}
+			/*if vt.Online && time.Now().Unix()-vt.Times > num {
+				if vt.FD.GetGRunFlag() {
+					vt.FD.ExitClient() //CloseClient()
+				}
+				//this.Remove(i)
+				//v.Online = false
+			}*/
+			return true
+		})
+	}
+}

+ 56 - 0
SERVER/Meter_Service/dataStruct/mapDtu.go

@@ -0,0 +1,56 @@
+package dataStruct
+
+type DMapDtuDev struct {
+	Map *DMapKv
+}
+
+func NewDMapDtuDev() *DMapDtuDev {
+	cm := &DMapDtuDev{
+		Map: NewDMapKv(),
+	}
+	return cm
+}
+
+func (cm *DMapDtuDev) Add(key string, val *DTUDeviceState) {
+	cm.Map.Add(key, val)
+}
+
+func (cm *DMapDtuDev) Remove(key string) {
+	cm.Map.Remove(key)
+}
+
+func (cm *DMapDtuDev) Get(key string) (*DTUDeviceState, bool) {
+	v, ok := cm.Map.Get(key)
+	if ok {
+		return v.(*DTUDeviceState), true
+	} else {
+		var t *DTUDeviceState
+		return t, false
+	}
+}
+
+// AlterKey 修改key
+func (cm *DMapDtuDev) AlterKey(before, after string) bool {
+	if before == after {
+		return true
+	}
+	if v, ok := cm.Map.Get(before); !ok {
+		return false
+	} else {
+		if _, ok = cm.Map.Get(after); ok {
+			return false
+		}
+		val := v.(*DTUDeviceState)
+		cm.Map.Add(after, val)
+		cm.Map.Remove(before)
+		return true
+	}
+}
+
+func (cm *DMapDtuDev) Len() int {
+	return cm.Map.Len()
+}
+
+func (cm *DMapDtuDev) Clean() {
+	cm.Map.Clean()
+}

+ 27 - 0
SERVER/Meter_Service/dataStruct/mapInterface.go

@@ -0,0 +1,27 @@
+package dataStruct
+
+import "sync"
+
+type DMapInterface struct {
+	RWLock sync.Mutex
+	Map    *sync.Map
+}
+
+func NewDMapInterface() *DMapInterface {
+	m := &DMapInterface{
+		Map: new(sync.Map),
+	}
+	return m
+}
+
+func (m *DMapInterface) Add(key interface{}, val interface{}) {
+	m.Map.Store(key, val)
+}
+
+func (m *DMapInterface) Remove(key interface{}) {
+	m.Map.Delete(key)
+}
+
+func (m *DMapInterface) Get(key interface{}) (interface{}, bool) {
+	return m.Map.Load(key)
+}

+ 74 - 0
SERVER/Meter_Service/dataStruct/meterCalcParam.go

@@ -0,0 +1,74 @@
+package dataStruct
+
+type MeterCalcParam struct {
+	Id             string         //电表编码
+	Time           int64          //计算日期
+	PowerRate      CalcPowerRate  //负荷率
+	TotalEnergy    CalcDataEnergy //总电能
+	DayTotalEnergy CalcDataEnergy //日总电能
+	isClean        bool
+}
+
+// CalcPowerRate 负荷率
+type CalcPowerRate struct {
+	SumPower float32 //有功功率总和
+	MaxPower float32 //最大有功功率
+	Count    int     //有功功率个数
+}
+
+// CalcDataEnergy 电能
+type CalcDataEnergy struct {
+	Tp float32
+	Tq float32
+	Fp float32
+	Fq float32
+}
+
+// CalcAndSetPowerRate 计算负荷率
+func (m *MeterCalcParam) CalcAndSetPowerRate(colData *CollectData) float32 {
+	m.PowerRate.Count++
+	m.PowerRate.SumPower += colData.P
+	if m.PowerRate.MaxPower < colData.P {
+		m.PowerRate.MaxPower = colData.P
+	}
+	return m.CalcRate()
+}
+
+// CalcRate 计算负荷率
+func (m *MeterCalcParam) CalcRate() float32 {
+	pv := float32(0)
+	if m.PowerRate.MaxPower > float32(0.0001) && m.PowerRate.Count > 0 {
+		pv = m.PowerRate.SumPower / (float32(m.PowerRate.Count) * m.PowerRate.MaxPower)
+	}
+	return pv
+}
+
+// ClearDayEnergy 日总电能清零
+func (m *MeterCalcParam) ClearDayEnergy() {
+	m.DayTotalEnergy.Tp = 0
+	m.DayTotalEnergy.Tq = 0
+	m.DayTotalEnergy.Fp = 0
+	m.DayTotalEnergy.Fq = 0
+	m.isClean = true
+}
+
+// CalcAndSetDayEnergy 计算并设置日电能
+func (m *MeterCalcParam) CalcAndSetDayEnergy(colData *CollectData) {
+	if m.isClean {
+		m.isClean = false
+		return
+	}
+	m.DayTotalEnergy.Tp += colData.Tps - m.TotalEnergy.Tp
+	m.DayTotalEnergy.Tq += colData.Tqs - m.TotalEnergy.Tq
+	m.DayTotalEnergy.Fp += colData.Fps - m.TotalEnergy.Fp
+	m.DayTotalEnergy.Fq += colData.Fqs - m.TotalEnergy.Fq
+
+}
+
+// SetTotalEnergy 设置总电能
+func (m *MeterCalcParam) SetTotalEnergy(colData *CollectData) {
+	m.TotalEnergy.Tp = colData.Tps
+	m.TotalEnergy.Tq = colData.Tqs
+	m.TotalEnergy.Fp = colData.Fps
+	m.TotalEnergy.Fq = colData.Fqs
+}

+ 7 - 0
SERVER/Meter_Service/dataStruct/parsingData.go

@@ -0,0 +1,7 @@
+package dataStruct
+
+type ParsingDataConfig struct {
+	Origin uint16 //起始地址
+	Amount uint16 //寄存器数量
+	Method func([]byte, *CollectData)
+}

+ 79 - 0
SERVER/Meter_Service/dataStruct/struct.go

@@ -0,0 +1,79 @@
+package dataStruct
+
+import (
+	"log"
+	"time"
+	"unicode/utf8"
+)
+
+// DTU管理命令
+const (
+	CmdRegister  = iota // 注册
+	CmdHeartBeat        // 心跳
+	CmdCollect          // 采集
+)
+
+type DTUManageCmd struct {
+	SN   string `json:"sn"`
+	Cmd  int    `json:"cmd"`
+	Data []byte `json:"data"`
+}
+
+// DmcUnmarshal 解析DTU上报
+// 注册和心跳格式:注册包@202402200322 心跳包 $202402200322
+// SN长度为8-16个字符
+func (dmc *DTUManageCmd) DmcUnmarshal(data []byte) {
+	size := utf8.RuneCount(data)
+	log.Println("DTU采集:", data)
+	// SN 长度在 8-16个字符 (注册包在9-17个字符)
+	if size > 8 && size < 18 {
+		if data[0] == '@' {
+			dmc.Cmd = CmdRegister
+			dmc.SN = string(data[1:])
+			return
+		} else if data[0] == '$' {
+			dmc.Cmd = CmdHeartBeat
+			dmc.SN = string(data[1:])
+			return
+		}
+	}
+	dmc.Data = data
+	dmc.Cmd = CmdCollect
+}
+
+type DtuRegisterChanMsg struct {
+	Sn    string
+	Value *ClientState
+}
+
+type DateTime struct {
+	Year   int
+	Month  time.Month
+	Day    int
+	Hour   int
+	Minute int
+	Second int
+}
+
+// DataToTimeStamp 日期转时间戳
+func (dt *DateTime) DataToTimeStamp() int64 {
+	return time.Date(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, 0, time.Local).Unix()
+}
+
+// TimeStampToData 时间戳转日期
+func (dt *DateTime) TimeStampToData(t int64) *DateTime {
+	v := time.Unix(t, 0)
+	m := &DateTime{}
+	m.Year = v.Year()
+	m.Month = v.Month()
+	m.Day = v.Day()
+	m.Hour = v.Hour()
+	m.Minute = v.Minute()
+	m.Second = v.Second()
+	return m
+}
+
+type MeterRef struct {
+	PvRef float32
+	LvRef float32
+}

+ 24 - 0
SERVER/Meter_Service/database/appApi/app.go

@@ -0,0 +1,24 @@
+package appApi
+
+type App struct {
+	AppId     int
+	AppSecret string
+	AppName   string
+	Host      string
+	isDelete  int
+	Apis      []*AppApi
+}
+
+func NewApp() *App {
+	return &App{
+		isDelete: 0,
+	}
+}
+
+func (m *App) SetDelete() {
+	m.isDelete = 1
+}
+
+func (m *App) GetDelete() int {
+	return m.isDelete
+}

+ 8 - 0
SERVER/Meter_Service/database/appApi/appApi.go

@@ -0,0 +1,8 @@
+package appApi
+
+type AppApi struct {
+	AppId int
+	Type  string
+	Host  string
+	Url   string
+}

+ 86 - 0
SERVER/Meter_Service/database/appApi/db.go

@@ -0,0 +1,86 @@
+package appApi
+
+import (
+	"MeterService/core/config"
+	"MeterService/core/db"
+	"strconv"
+)
+
+type DbAppApi struct {
+	Db *db.MyDB
+}
+
+func (m *DbAppApi) GetList() ([]interface{}, error) {
+	array := make([]interface{}, 0)
+	arr, ok := m.Db.Query("SELECT api.AppId, api.Type, api.Url,app.Host FROM AppInfo AS app LEFT JOIN AppApiInfo AS api ON app.AppId = api.AppId WHERE app.IsDelete = 0 AND api.AppId IS NOT NULL")
+	if ok && arr != nil && len(arr) > 0 {
+		for _, v := range arr {
+			appId, _ := strconv.Atoi(v["AppId"])
+			value := &AppApi{
+				AppId: appId,
+				Type:  v["Type"],
+				Host:  v["Host"],
+				Url:   v["Url"],
+			}
+			array = append(array, value)
+		}
+	}
+	return array, nil
+}
+
+func (m *DbAppApi) AddOrUpdate(v interface{}) error {
+	d := v.(*App)
+	if d.AppId == 0 {
+		if _, err := m.Db.Exec("INSERT INTO AppInfo(appSecret,appName, host,isDelete) VALUES(?,?,?,?)", d.AppSecret, d.AppName, d.Host, 0); err != nil {
+			return err
+		}
+	} else {
+		if _, err := m.Db.Exec("UPDATE AppInfo SET appSecret=?,appName=?,host=?,isDelete=? WHERE AppId=?", d.AppSecret, d.AppName, d.Host, d.GetDelete(), d.AppId); err != nil {
+			return err
+		}
+		if _, err := m.Db.Exec("DELETE FROM AppApiInfo WHERE appId=?", d.AppId); err != nil {
+			return err
+		}
+	}
+	if len(d.Apis) > 0 {
+		for _, v := range d.Apis {
+			_, err := m.Db.Exec("INSERT INTO AppApiInfo(appId, url, type) VALUES(?,?,?)", d.AppId, v.Url, v.Type)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (m *DbAppApi) Delete(v interface{}) error {
+	d := v.(*App)
+	_, err := m.Db.Exec("UPDATE AppInfo SET isDelete=? WHERE AppId=?", 1, d.AppId)
+	return err
+}
+
+func (m *DbAppApi) CheckSecret(d *App) bool {
+	if config.C.Vber.Mode == "debug" && d.AppSecret == "" {
+		return true
+	}
+	appId := d.AppId
+	if arr, ok := m.Db.Query("SELECT AppId, AppSecret FROM AppInfo  WHERE  appId=? AND IsDelete = 0", appId); !ok {
+		return false
+	} else {
+		if len(arr) != 1 {
+			return false
+		}
+		for _, v := range arr {
+			if v["AppSecret"] == d.AppSecret {
+				return true
+			}
+		}
+		return false
+	}
+}
+
+func NewAppApiDb() *DbAppApi {
+	return &DbAppApi{
+		Db: db.GetDb(),
+	}
+}

+ 54 - 0
SERVER/Meter_Service/database/meterCalcParam/db.go

@@ -0,0 +1,54 @@
+package meterCalcParam
+
+import (
+	"MeterService/core/db"
+	"database/sql"
+)
+
+//type SqlDb interface {
+//	GetList() ([]interface{}, error)
+//	AddOrUpdate(v interface{}) error
+//}
+
+type DbMeterCalcParam struct {
+	Db *db.MyDB
+}
+
+func (m *DbMeterCalcParam) GetList() ([]interface{}, error) {
+	array := make([]interface{}, 0)
+	rows, err := m.Db.DB.Query("SELECT mcp.Id, mcp.Time, mcp.SumPower, mcp.MaxPower, mcp.Count, mcp.Tps, mcp.Tqs, mcp.Fps, mcp.Fqs, mcp.Tpe, mcp.Tqe, mcp.Fpe, mcp.Fqe FROM MeterCalcParam AS mcp")
+	defer func(rows *sql.Rows) {
+		_ = rows.Close()
+	}(rows)
+	for rows.Next() {
+		//var param dataStruct.MeterCalcParam
+		//err = rows.Scan(&param.Id, &param.Time, &param.PowerRate.SumPower, &param.PowerRate.MaxPower, &param.PowerRate.Count,
+		//	&param.DayTotalEnergy.Tp, &param.DayTotalEnergy.Tq, &param.DayTotalEnergy.Fp, &param.DayTotalEnergy.Fq,
+		//	&param.TotalEnergy.Fp, &param.TotalEnergy.Fq, &param.TotalEnergy.Tp, &param.TotalEnergy.Tq)
+		var param MeterCalcParam
+		err = rows.Scan(&param.Id, &param.Time, &param.SumPower, &param.MaxPower, &param.Count, &param.Tps, &param.Tqs, &param.Fps, &param.Fqs, &param.Tpe, &param.Tqe, &param.Fpe, &param.Fqe)
+		if err != nil {
+			return nil, err
+		}
+		array = append(array, &param)
+	}
+	return array, nil
+}
+
+func (m *DbMeterCalcParam) AddOrUpdate(v interface{}) error {
+	d := v.(*MeterCalcParam)
+	ret, ok := m.Db.Query("SELECT id FROM MeterCalcParam WHERE id=?", d.Id)
+	if ok || len(ret) > 0 {
+		_, err := m.Db.Exec("UPDATE MeterCalcParam SET Time=?, SumPower=?, MaxPower=?, Count=?, Tps=?, Tqs=?, Fps=?, Fqs=?, Tpe=?, Tqe=?, Fpe=?, Fqe=? WHERE Id=?", d.Time, d.SumPower, d.MaxPower, d.Count, d.Tps, d.Tqs, d.Fps, d.Fqs, d.Tpe, d.Tqe, d.Fpe, d.Fqe, d.Id)
+		return err
+	} else {
+		_, err := m.Db.Exec("INSERT INTO MeterCalcParam(Id, Time, SumPower, MaxPower, Count, Tps, Tqs, Fps, Fqs, Tpe, Tqe, Fpe, Fqe) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)", d.Id, d.Time, d.SumPower, d.MaxPower, d.Count, d.Tps, d.Tqs, d.Fps, d.Fqs, d.Tpe, d.Tqe, d.Fpe, d.Fqe)
+		return err
+	}
+}
+
+func NewMeterCalcParamDb() *DbMeterCalcParam {
+	return &DbMeterCalcParam{
+		Db: db.GetDb(),
+	}
+}

+ 17 - 0
SERVER/Meter_Service/database/meterCalcParam/meterCalcParam.go

@@ -0,0 +1,17 @@
+package meterCalcParam
+
+type MeterCalcParam struct {
+	Id       string  //电表编码
+	Time     int64   //计算日期
+	SumPower float32 //有功功率总和
+	MaxPower float32 //最大有功功率
+	Count    int     //有功功率个数
+	Tps      float32
+	Tqs      float32
+	Fps      float32
+	Fqs      float32
+	Tpe      float32
+	Tqe      float32
+	Fpe      float32
+	Fqe      float32
+}

BIN
SERVER/Meter_Service/database/vber.db


+ 66 - 0
SERVER/Meter_Service/go.mod

@@ -0,0 +1,66 @@
+module MeterService
+
+go 1.21
+
+require (
+	github.com/fsnotify/fsnotify v1.7.0
+	github.com/gin-gonic/gin v1.9.1
+	github.com/go-redis/redis v6.15.9+incompatible
+	github.com/goburrow/modbus v0.1.0
+	github.com/goburrow/serial v0.1.0
+	github.com/jinzhu/gorm v1.9.16
+	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
+	github.com/spf13/pflag v1.0.5
+	github.com/spf13/viper v1.18.2
+	go.uber.org/zap v1.21.0
+)
+
+require (
+	github.com/bytedance/sonic v1.9.1 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.14.0 // indirect
+	github.com/go-sql-driver/mysql v1.5.0 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/google/go-cmp v0.6.0 // indirect
+	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jonboulle/clockwork v0.4.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/lestrrat-go/strftime v1.0.6 // indirect
+	github.com/magiconair/properties v1.8.7 // indirect
+	github.com/mattn/go-isatty v0.0.19 // indirect
+	github.com/mattn/go-sqlite3 v1.14.0 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/onsi/ginkgo v1.16.5 // indirect
+	github.com/onsi/gomega v1.31.1 // indirect
+	github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/sagikazarmark/locafero v0.4.0 // indirect
+	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+	github.com/sourcegraph/conc v0.3.0 // indirect
+	github.com/spf13/afero v1.11.0 // indirect
+	github.com/spf13/cast v1.6.0 // indirect
+	github.com/subosito/gotenv v1.6.0 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.11 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	go.uber.org/multierr v1.9.0 // indirect
+	golang.org/x/arch v0.3.0 // indirect
+	golang.org/x/crypto v0.16.0 // indirect
+	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+	golang.org/x/net v0.19.0 // indirect
+	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	google.golang.org/protobuf v1.31.0 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/yaml.v2 v2.3.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 266 - 0
SERVER/Meter_Service/go.sum

@@ -0,0 +1,266 @@
+github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
+github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
+github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
+github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/goburrow/modbus v0.1.0 h1:DejRZY73nEM6+bt5JSP6IsFolJ9dVcqxsYbpLbeW/ro=
+github.com/goburrow/modbus v0.1.0/go.mod h1:Kx552D5rLIS8E7TyUwQ/UdHEqvX5T8tyiGBTlzMcZBg=
+github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
+github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
+github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
+github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
+github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
+github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
+github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
+github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
+github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
+github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
+github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
+github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
+github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
+github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
+go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
+golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
+golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 56 - 0
SERVER/Meter_Service/main.go

@@ -0,0 +1,56 @@
+package main
+
+import (
+	_ "MeterService/core/config" // 加载配置文件,需要放在第一行
+	"MeterService/core/logger"   // 加载logger,放第二行
+
+	"MeterService/core/db"
+	"MeterService/core/db/sqlite"
+
+	"MeterService/data"
+	"MeterService/meter/proto"
+	"MeterService/service/downStreamService"
+	"MeterService/service/webService"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+)
+
+func main() {
+
+	// 连接mysql数据库
+	//DB := db.OpenDb()
+	//defer db.CloseDb(DB)
+
+	// 连接sqlite
+	db.OpenDb(sqlite.OpenSQLite)
+	defer db.CloseDb()
+
+	// redis
+	db.InitRedis()
+
+	// 加载电表协议
+	proto.MeterProtoInit()
+
+	// 初始化电表数据
+	if data.InitData() {
+		logger.Debug("初始化电表数据成功!")
+		// 启动web服务
+		go webService.NewWebServer()
+		// 启动downStream服务
+		go downStreamService.NewDownStreamService()
+	}
+
+	go func() {
+		ch := make(chan os.Signal)
+		signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGQUIT)
+		<-ch
+		logger.Info("即将退出!")
+		os.Exit(0)
+	}()
+	logger.Info("程序运行中...")
+	for {
+		time.Sleep(5 * time.Minute)
+	}
+}

+ 25 - 0
SERVER/Meter_Service/meter/proto/proto.go

@@ -0,0 +1,25 @@
+package proto
+
+var (
+	meterProto = make([]string, 0) //电表协议列表
+	platProto  = make([]string, 0) //上报协议列表
+)
+
+// MeterProtoInit 初始化电表协议列表和上报协议列表
+func MeterProtoInit() {
+	//在这里添加支持的电表协议列表
+	meterProto = append(meterProto, "ADW300")
+
+	//在这里添加支持的上报协议列表
+	platProto = append(platProto, "YC-HJ212")
+}
+
+// GetMeterProtoList 获取电表协议列表
+func GetMeterProtoList() []string {
+	return meterProto
+}
+
+// GetPlatProtoList 获取上报协议列表
+func GetPlatProtoList() []string {
+	return platProto
+}

+ 123 - 0
SERVER/Meter_Service/service/downStreamService/collect.go

@@ -0,0 +1,123 @@
+package downStreamService
+
+import (
+	"MeterService/core/logger"
+	"MeterService/core/utils"
+	"MeterService/data"
+	"MeterService/dataStruct"
+	"MeterService/database/meterCalcParam"
+	"MeterService/service/downStreamService/proto/acrel"
+	"MeterService/service/rtuService"
+)
+
+func collectData(sn string, config *dataStruct.DtuConfig) []*dataStruct.CollectData {
+	array := make([]*dataStruct.CollectData, 0)
+
+	meterRef := &dataStruct.MeterRef{}
+	logger.Info("【%s】开始采集数据 ", sn)
+	logger.Debug("【%s】开始采集数据 %v", sn, config)
+
+	if !config.Enable || config.Secs < 1 {
+		return array
+	}
+
+	client, ok := data.DtuMapState.Get(sn)
+	if !ok {
+		logger.Error("采集数据失败,[%s]客户端不存在", sn)
+		return array
+	}
+
+	if !client.Online {
+		logger.Error("采集数据失败,[%s]客户端离线", sn)
+	}
+
+	for _, cfg := range config.Slave {
+		if cfg.Addr < 1 || cfg.Addr > 254 {
+			logger.Error("采集数据失败,[%s]客户端地址错误: %d", sn, cfg.Addr)
+			continue
+		}
+		bAddr := utils.IntoByte(cfg.Addr)
+		w := rtuService.NewRtuNetPgr(bAddr)
+		w.SetClientState(client)
+		w.SetSerialNumber(sn)
+		meterRef.LvRef = 220
+		meterRef.PvRef = 380
+		if cfg.LvRef > 0 {
+			meterRef.LvRef = cfg.LvRef
+		}
+		if cfg.PvRef > 0 {
+			meterRef.PvRef = cfg.PvRef
+		}
+		var (
+			colData *dataStruct.CollectData
+			err     error
+		)
+		switch cfg.MType {
+		case "ADW300":
+			colData, err = acrel.CollectADW300(w, meterRef)
+			if err != nil {
+				logger.Error("采集数据失败, [%s][%s]客户端采集数据错误: %s", sn, cfg.MType, err.Error())
+				continue
+			}
+			processCollectData(colData, cfg)
+			array = append(array, colData)
+		default:
+		}
+
+	}
+	logger.Debug("采集数据完成【%s】 %v", sn, array)
+	return array
+}
+
+func processCollectData(colData *dataStruct.CollectData, cfg *dataStruct.DtuSlave) {
+	// 从机的配置需要保存到采集数据中,上报需要使用配置
+	colData.Slave = cfg
+	// 计算电表数据
+	logger.Debug("计算电表数据: %s", cfg.NO)
+	calc := &dataStruct.MeterCalcParam{}
+	tms := utils.NowDataToTimeStamp()
+	if x, ok := data.CalcParam.Get(cfg.NO); ok {
+		calc = x.(*dataStruct.MeterCalcParam)
+	} else {
+		calc.PowerRate.Count = 1
+		calc.PowerRate.MaxPower = colData.P
+		calc.PowerRate.SumPower = colData.P
+		calc.ClearDayEnergy()
+	}
+	t := (&dataStruct.DateTime{}).TimeStampToData(tms)
+	if t.Hour == 0 && t.Minute == 0 {
+		calc.PowerRate.Count = 1
+		calc.PowerRate.MaxPower = colData.P
+		calc.PowerRate.SumPower = colData.P
+		calc.ClearDayEnergy()
+	}
+	// 计算电表数据
+	logger.Debug("[%s] 计算前: [%v]", cfg.NO, calc)
+	colData.CalcWithParam(calc)
+	calc.Time = tms
+	logger.Debug("[%s] 计算完成: [%v]", cfg.NO, calc)
+	// 更新电表计算参数
+	db := meterCalcParam.NewMeterCalcParamDb()
+	data.CalcParam.Add(cfg.NO, calc)
+	if db != nil {
+		calcEt := &meterCalcParam.MeterCalcParam{
+			Id:       cfg.NO,
+			Time:     tms,
+			SumPower: calc.PowerRate.SumPower,
+			MaxPower: calc.PowerRate.MaxPower,
+			Count:    calc.PowerRate.Count,
+			Tps:      calc.TotalEnergy.Tp,
+			Tqs:      calc.TotalEnergy.Tq,
+			Fps:      calc.TotalEnergy.Fp,
+			Fqs:      calc.TotalEnergy.Fq,
+			Tpe:      calc.DayTotalEnergy.Tp,
+			Tqe:      calc.DayTotalEnergy.Tq,
+			Fpe:      calc.DayTotalEnergy.Fp,
+			Fqe:      calc.DayTotalEnergy.Fq,
+		}
+		err := db.AddOrUpdate(calcEt)
+		if err != nil {
+			logger.Error("更新电表计算参数失败: %s", err.Error())
+		}
+	}
+}

+ 346 - 0
SERVER/Meter_Service/service/downStreamService/proto/acrel/acrel.go

@@ -0,0 +1,346 @@
+package acrel
+
+import (
+	"MeterService/core/logger"
+	"MeterService/dataStruct"
+	"MeterService/service/rtuService"
+	"reflect"
+	"runtime"
+)
+
+var (
+	adw300Meter = []dataStruct.ParsingDataConfig{
+		{Origin: 0x0E, Amount: 56, Method: adw300A04C56},
+		{Origin: 0x12E, Amount: 12, Method: adw300A12eC12},
+		{Origin: 0x7A, Amount: 60, Method: adw300A7aC60},
+		{Origin: 0xB6, Amount: 60, Method: adw300Ab6C60},
+		{Origin: 0xF2, Amount: 60, Method: adw300Af2C60},
+		{Origin: 0x15A, Amount: 13, Method: adw300A15aC13},
+	}
+)
+
+func CollectADW300(w rtuService.RtuNetPgr, ref *dataStruct.MeterRef) (*dataStruct.CollectData, error) {
+	colData := &dataStruct.CollectData{
+		MeterRef: ref,
+	}
+	for _, v := range adw300Meter {
+		if adu, err := w.GetHoldingRegs(v.Origin, v.Amount); err != nil {
+			logger.Error("ADW300 采集失败[%s]  ERROR:%v", runtime.FuncForPC(reflect.ValueOf(v.Method).Pointer()).Name(), err)
+			return colData, err
+		} else {
+			logger.Debug("======》ADU:%v", adu)
+			v.Method(adu, colData)
+		}
+	}
+	return colData, nil
+}
+
+func adw300A04C56(adu []byte, s *dataStruct.CollectData) {
+	var (
+		index = 0
+		m     int16
+		u     int32
+	)
+	// 电压变比
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.PT = int(m)
+	// 电流变比
+	index += 2
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.CT = int(m)
+	index += 2
+	//N相温度
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.TemperatureZ = float32(m) * float32(0.1)
+	index += 2
+	index += 6
+	//A相电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Ua = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//B相电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Ub = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//C相电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Uc = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//AB线电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Uab = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//BC线电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Ubc = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//CA相电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Uca = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+
+	//A相电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Ia = float32(m) * float32(0.01) * float32(s.CT)
+	index += 2
+	//B相电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Ib = float32(m) * float32(0.01) * float32(s.CT)
+	index += 2
+	//C相电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Ic = float32(m) * float32(0.01) * float32(s.CT)
+	index += 2
+	//三相电流矢量和
+	index += 2
+
+	//A相有功功率
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Pa = float32(u) * float32(0.001) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//B相有功功率
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Pb = float32(u) * float32(0.001) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//C相有功功率
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Pc = float32(u) * float32(0.001) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//总有功功率
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.P = float32(u) * float32(0.001) * float32(s.CT) * float32(s.PT)
+	index += 4
+
+	//A相无功功率
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Qa = float32(u) * float32(0.001) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//B相无功功率
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Qb = float32(u) * float32(0.001) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//C相无功功率
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Qc = float32(u) * float32(0.001) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//总无功功率
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Q = float32(u) * float32(0.001) * float32(s.CT) * float32(s.PT)
+	index += 4
+
+	//A相视在功率
+	index += 4
+	//B相视在功率
+	index += 4
+	//C相视在功率
+	index += 4
+	//总视在功率
+	index += 4
+
+	//A相功率因数
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Pfa = float32(m) * float32(0.001)
+	index += 2
+	//B相功率因数
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Pfb = float32(m) * float32(0.001)
+	index += 2
+	//C相功率因数
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Pfc = float32(m) * float32(0.001)
+	index += 2
+	//总功率因数
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Pf = float32(m) * float32(0.001)
+	index += 2
+	//DI
+	index += 2
+	//频率
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.Freq = float32(m) * float32(0.01)
+	index += 2
+	//组合有功总电能
+	index += 4
+	//正向有功电能
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Tps = float32(u) * float32(0.01) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//反向有功电能
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Fps = float32(u) * float32(0.01) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//正向无功电能
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Tqs = float32(u) * float32(0.01) * float32(s.CT) * float32(s.PT)
+	index += 4
+	//反向无功电能
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	s.Fqs = float32(u) * float32(0.01) * float32(s.CT) * float32(s.PT)
+	//index += 4
+	s.CalcDiff()
+
+}
+
+// 基波、谐波
+func adw300A12eC12(adu []byte, s *dataStruct.CollectData) {
+	var (
+		index = 0
+		m     int16
+	)
+	//A相基波电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.BaseUa = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//B相基波电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.BaseUb = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//C相基波电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.BaseUc = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+
+	//A相谐波电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.HarUa = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//B相谐波电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.HarUb = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+	//C相谐波电压
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.HarUc = float32(m) * float32(0.1) * float32(s.PT)
+	index += 2
+
+	//A相基波电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.BaseIa = float32(m) * float32(0.01) * float32(s.CT)
+	index += 2
+	//B相基波电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.BaseIb = float32(m) * float32(0.01) * float32(s.CT)
+	index += 2
+	//C相基波电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.BaseIc = float32(m) * float32(0.01) * float32(s.CT)
+	index += 2
+
+	//A相谐波电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.HarIa = float32(m) * float32(0.1) * float32(s.CT)
+	index += 2
+	//B相谐波电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.HarIb = float32(m) * float32(0.1) * float32(s.CT)
+	index += 2
+	//C相谐波电流
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	s.HarIc = float32(m) * float32(0.1) * float32(s.CT)
+}
+
+// 0x7A,60
+// A相电压分次谐波(2-31次)
+// B相电压分次谐波(2-31次)
+func adw300A7aC60(adu []byte, s *dataStruct.CollectData) {
+	var (
+		index = 2
+		m     int16
+	)
+	//A相电压分次谐波(2-31次)
+	for i := 0; i < 15; i++ {
+		m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+		s.Hua[i] = float32(m) * float32(0.01)
+		index += 4
+	}
+	//B相电压分次谐波(2-31次)
+	index = 2*30 + 2
+	for i := 0; i < 15; i++ {
+		m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+		s.Hub[i] = float32(m) * float32(0.01)
+		index += 4
+	}
+}
+
+// 0xB6,60
+// C相电压分次谐波(2-31次)
+// A相电流分次谐波(2-31次)
+func adw300Ab6C60(adu []byte, s *dataStruct.CollectData) {
+	var (
+		index = 2
+		m     int16
+	)
+	//C相电压分次谐波(2-31次)
+	for i := 0; i < 15; i++ {
+		m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+		s.Huc[i] = float32(m) * float32(0.01)
+		index += 4
+	}
+	//A相电流分次谐波(2-31次)
+	index = 2*30 + 2
+	for i := 0; i < 15; i++ {
+		m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+		s.Hia[i] = float32(m) * float32(0.01)
+		index += 4
+	}
+}
+
+// 0xF2,60
+// B相电流分次谐波(2-31次)
+// C相电流分次谐波(2-31次)
+func adw300Af2C60(adu []byte, s *dataStruct.CollectData) {
+	var (
+		index = 2
+		m     int16
+	)
+	//index = 2
+	for i := 0; i < 15; i++ {
+		m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+		s.Hub[i] = float32(m) * float32(0.01)
+		index += 4
+	}
+	//C相电流分次谐波(2-31次)
+	index = 2*30 + 2
+	for i := 0; i < 15; i++ {
+		m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+		s.Hic[i] = float32(m) * float32(0.01)
+		index += 4
+	}
+}
+
+func adw300A15aC13(adu []byte, data *dataStruct.CollectData) {
+	var (
+		index = 0
+		m     int16
+		u     int32
+	)
+	//当前正向有功需量
+	u = int32(uint32(adu[index])<<24 | uint32(adu[index+1])<<16 | uint32(adu[index+2])<<8 | uint32(adu[index+3]))
+	data.Dp = float32(u) * float32(0.001) * float32(data.CT) * float32(data.PT)
+	//当前反向有功需量
+	index += 4
+	//当前正向无功需量
+	index += 4
+	//当前反向无功需量
+	index += 4
+	//电压不平衡度
+	index += 4
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	data.UUnbalance = float32(m) * float32(0.01)
+	//电流不平衡度
+	index += 2
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	data.IUnbalance = float32(m) * float32(0.01)
+	//A相温度
+	index += 2
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	data.TemperatureA = float32(m) * float32(0.1)
+	//B相温度
+	index += 2
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	data.TemperatureB = float32(m) * float32(0.1)
+	//C相温度
+	index += 2
+	m = int16(uint16(adu[index])<<8 | uint16(adu[index+1]))
+	data.TemperatureC = float32(m) * float32(0.1)
+}

+ 136 - 0
SERVER/Meter_Service/service/downStreamService/proto/report/report.go

@@ -0,0 +1,136 @@
+package report
+
+import (
+	"MeterService/core/logger"
+	"MeterService/dataStruct"
+	"MeterService/service/reportService"
+	"fmt"
+	"strconv"
+	"time"
+)
+
+func YcHj212Report(dataArray []*dataStruct.CollectData, config *dataStruct.DtuConfig) {
+	bs := &dataStruct.HJBaseInfo{
+		Host: config.IP,
+		ST:   config.St,
+		CN:   config.Cn,
+		PW:   config.Pw,
+		MN:   config.Mn,
+		Port: strconv.Itoa(config.Port),
+	}
+	if strArray, ok := packYcHj212(bs, dataArray); ok {
+		for _, str := range strArray {
+
+			if receive, err := reportService.UpToServer(bs.Host, bs.Port, str); err != nil {
+				logger.Error("[%s:%s]上报数据失败: [ %s ] %s", bs.Host, bs.Port, str, err.Error())
+			} else {
+				logger.Debug("上报数据 SEND -> %s", str)
+				logger.Debug("上报数据 RECE <- %s", receive)
+			}
+		}
+	}
+}
+
+func packYcHj212(bs *dataStruct.HJBaseInfo, dataArray []*dataStruct.CollectData) ([]string, bool) {
+	array := make([]string, 0)
+	if len(dataArray) == 0 {
+		return array, false
+	}
+	now := time.Now()
+	nt := fmt.Sprintf("%04d%02d%02d%02d%02d00", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute())
+	for _, d := range dataArray {
+		body := fmt.Sprintf("%s=%s", d.Slave.GetBmYzKey("p"), getBmYzFloatValue(d.P)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("pa"), getBmYzFloatValue(d.Pa)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("pb"), getBmYzFloatValue(d.Pb)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("pc"), getBmYzFloatValue(d.Pc)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("q"), getBmYzFloatValue(d.Q)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("qa"), getBmYzFloatValue(d.Qa)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("qb"), getBmYzFloatValue(d.Qb)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("qc"), getBmYzFloatValue(d.Qc)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("pf"), getBmYzFloatValue(d.Pf)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("pfa"), getBmYzFloatValue(d.Pfa)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("pfb"), getBmYzFloatValue(d.Pfb)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("pfc"), getBmYzFloatValue(d.Pfc)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ia"), getBmYzFloatValue(d.Ia)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ib"), getBmYzFloatValue(d.Ib)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ic"), getBmYzFloatValue(d.Ic)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("iz"), getBmYzFloatValue(d.Iz)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ua"), getBmYzFloatValue(d.Ua)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ub"), getBmYzFloatValue(d.Ub)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("uc"), getBmYzFloatValue(d.Uc)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("uab"), getBmYzFloatValue(d.Uab)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ubc"), getBmYzFloatValue(d.Ubc)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("uca"), getBmYzFloatValue(d.Uca)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("dp"), getBmYzFloatValue(d.Dp)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("pv"), getBmYzFloatValue(d.Pv)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("f"), getBmYzFloatValue(d.Freq)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("fw"), getBmYzFloatValue(d.Fw)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("tps"), getBmYzFloatValue(d.Tps)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("tqs"), getBmYzFloatValue(d.Tqs)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("fps"), getBmYzFloatValue(d.Fps)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("fqs"), getBmYzFloatValue(d.Fqs)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("tpe"), getBmYzFloatValue(d.Tpe)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("tqe"), getBmYzFloatValue(d.Tqe)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("fpe"), getBmYzFloatValue(d.Fpe)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("fqe"), getBmYzFloatValue(d.Fqe)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("uaw"), getBmYzFloatValue(d.Uaw)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ubw"), getBmYzFloatValue(d.Ubw)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ucw"), getBmYzFloatValue(d.Ucw)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("uabw"), getBmYzFloatValue(d.Uabw)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ubcw"), getBmYzFloatValue(d.Ubcw)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("ucaw"), getBmYzFloatValue(d.Ucaw)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("inbalance"), getBmYzFloatValue(d.IUnbalance)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("unbalance"), getBmYzFloatValue(d.UUnbalance)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("t"), getBmYzFloatValue(d.TemperatureA)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("t2"), getBmYzFloatValue(d.TemperatureB)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("t3"), getBmYzFloatValue(d.TemperatureC)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("t4"), getBmYzFloatValue(d.TemperatureZ)) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("hua"), packYcHj212Hx(d.Hua, d.HarUa, d.BaseUa, "baseu")) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("hub"), packYcHj212Hx(d.Hub, d.HarUb, d.BaseUb, "baseu")) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("huc"), packYcHj212Hx(d.Huc, d.HarUc, d.BaseUc, "baseu")) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("hia"), packYcHj212Hx(d.Hia, d.HarIa, d.BaseIa, "basei")) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("hib"), packYcHj212Hx(d.Hib, d.HarIb, d.BaseIb, "basei")) +
+			fmt.Sprintf("&%s=%s", d.Slave.GetBmYzKey("hic"), packYcHj212Hx(d.Hic, d.HarIc, d.BaseIc, "basei"))
+
+		str := fmt.Sprintf("st=%s;cn=%s;datatime=%s;cphh=&&tid=%s&%s&", bs.ST, bs.CN, nt, d.Slave.NO, body)
+		crc16 := calCRC16HJ212([]byte(str))
+		str1 := fmt.Sprintf("##00%04d", len(str)) + str + fmt.Sprintf("%04X", crc16) + "\r\n"
+		array = append(array, str1)
+	}
+	return array, true
+}
+
+func packYcHj212Hx(val [15]float32, hall, base float32, baseStr string) string {
+	ret := ""
+	for i, v := range val {
+		ret += fmt.Sprintf("h%d:%s,", i*2+1, getBmYzFloatValue(v))
+	}
+	ret += fmt.Sprintf("hall:%s,%s:%s", getBmYzFloatValue(hall), baseStr, getBmYzFloatValue(base))
+	return ret
+}
+
+func getBmYzFloatValue(value float32) string {
+	return strconv.FormatFloat(float64(value), 'f', 3, 32)
+}
+
+// HJ212 计算crc
+func calCRC16HJ212(data []byte) uint16 {
+	var (
+		crc  uint16 = 0xFFFF
+		iNum uint16 = 0
+	)
+	length := len(data)
+	for i := 0; i < length; i++ {
+		iNum = uint16(data[i])
+		crc = (crc >> 8) & 0x00FF
+		crc = crc ^ iNum
+		for j := 0; j < 8; j++ {
+			flag := crc % 2
+			crc = crc >> 1
+			if flag == 1 {
+				crc = crc ^ 0xA001
+			}
+		}
+	}
+	return crc
+}

+ 14 - 0
SERVER/Meter_Service/service/downStreamService/report.go

@@ -0,0 +1,14 @@
+package downStreamService
+
+import (
+	"MeterService/dataStruct"
+	"MeterService/service/downStreamService/proto/report"
+)
+
+func reportData(dataArray []*dataStruct.CollectData, config *dataStruct.DtuConfig) {
+	switch config.Protocol {
+	case "YC-HJ212":
+		go report.YcHj212Report(dataArray, config)
+	default:
+	}
+}

+ 159 - 0
SERVER/Meter_Service/service/downStreamService/service.go

@@ -0,0 +1,159 @@
+package downStreamService
+
+import (
+	"MeterService/core/config"
+	"MeterService/core/logger"
+	"MeterService/core/tcpserver"
+	"MeterService/data"
+	"MeterService/dataStruct"
+	"encoding/json"
+	"fmt"
+	"log"
+	"time"
+)
+
+func NewDownStreamService() {
+	logger.Info("启动DTU服务:%d", config.C.Vber.CmdPort)
+	server := tcpserver.New(fmt.Sprintf(":%d", config.C.Vber.CmdPort))
+	server.OnNewClient(func(conn *tcpserver.Client) {
+		logger.Info("新设备连接:%s", conn.GetClientHost())
+	})
+	server.OnNewMessage(func(conn *tcpserver.Client, msg []byte) {
+		//logger.Debug("[%s]收到新消息:%s", conn.GetClientHost(), string(msg))
+		logger.Debug("[%s]收到新消息[byte]:%v", conn.GetClientHost(), msg)
+		if dealDtuUpMsg(conn, msg) {
+			conn.PutRawRes(msg)
+		}
+	})
+	server.OnClientConnectionClosed(func(conn *tcpserver.Client, err error) {
+		dealDTUDisconnect(conn)
+	})
+
+	go runTran(data.TranSN)
+
+	server.Listen()
+}
+
+// 处理DTU上报数据
+func dealDtuUpMsg(conn *tcpserver.Client, msg []byte) bool {
+	var (
+		dtuMsg = &dataStruct.DTUManageCmd{}
+	)
+	dtuMsg.DmcUnmarshal(msg)
+	if dtuMsg.Cmd == dataStruct.CmdCollect {
+		return true
+	} else {
+		dealDtuRegisterHeartBeat(conn, dtuMsg)
+	}
+	return false
+}
+
+// 处理DTU注册包及心跳包
+func dealDtuRegisterHeartBeat(conn *tcpserver.Client, dtuMsg *dataStruct.DTUManageCmd) {
+	if dtuMsg.Cmd == 0 || dtuMsg.Cmd == 1 {
+		clientState, _ := data.DtuMapState.Get(dtuMsg.SN)
+		conn.SetClientRegisterID(dtuMsg.SN)
+		clientState.FD = conn
+		clientState.Online = true
+		clientState.UpdateTime()
+		data.DtuMapState.Add(dtuMsg.SN, clientState)
+		data.IpSN.Add(conn.GetClientHost(), dtuMsg.SN)
+		onlineSn, ok := data.OnlineSN.Get(dtuMsg.SN)
+		if !ok || onlineSn.FD.GetClientHost() != clientState.FD.GetClientHost() {
+			msg := &dataStruct.DtuRegisterChanMsg{
+				Sn:    dtuMsg.SN,
+				Value: clientState,
+			}
+			data.TranSN <- msg
+		}
+		if dtuMsg.Cmd == 0 {
+			logger.Info(dtuMsg.SN + "[" + clientState.FD.GetClientHost() + "] Register")
+		}
+		if dtuMsg.Cmd == 1 {
+			logger.Info(dtuMsg.SN + "[" + clientState.FD.GetClientHost() + "] HeartBeat")
+		}
+	} else {
+		logger.Error("Register || HeartBeat 失败")
+	}
+}
+
+// 处理DTU断开连接
+func dealDTUDisconnect(conn *tcpserver.Client) {
+	sn := conn.GetClientRegisterID()
+	addr := conn.GetClientHost()
+	logger.Info("[%s]设备断开连接:%s", addr, sn)
+	if sn != "" {
+		if client, ok := data.DtuMapState.Get(sn); ok && client.FD.GetClientHost() == addr {
+			client.Online = false
+			data.DtuMapState.Add(sn, client)
+			logger.Debug("[%s]设备离线:%s", addr, sn)
+		}
+		if client, ok := data.OnlineSN.Get(sn); ok && client.FD.GetClientHost() == addr {
+			data.OnlineSN.Remove(sn)
+		}
+	}
+}
+
+func runTran(msg <-chan *dataStruct.DtuRegisterChanMsg) {
+	lastMinute := time.Now().Minute() //最后更新时间
+	ticker := time.NewTicker(time.Second * 5)
+	defer ticker.Stop()
+	for {
+		select {
+		case msg := <-msg:
+			logger.Info("【更新设备配置】 SN:%s", msg.Sn)
+			updateOnlineConf(msg)
+		case nowTime := <-ticker.C:
+			minute := nowTime.Minute() //当前分钟
+			if minute != lastMinute {
+				count := 0
+				onlineDtu := make(map[string]*dataStruct.ClientState)
+
+				for _, v := range data.DtuMap.Map.Map {
+					vv := v.(*dataStruct.DTUDeviceState)
+					log.Println("===============", vv.Info.Name)
+				}
+				data.OnlineSN.Map.Range(func(key, value interface{}) bool {
+					v := value.(*dataStruct.ClientState)
+					dtuSn := key.(string)
+					logger.Debug("【检查在线设备配置】 SN:%s", dtuSn)
+					if v.Config != nil {
+						onlineDtu[dtuSn] = v
+					}
+					count++
+					return true
+				})
+				if count == 0 {
+					logger.Info("没有在线设备。")
+				}
+				for dtuSn, client := range onlineDtu {
+					if minute%client.Config.Secs == 0 {
+						// 采集上报数据
+						if dataArray := collectData(dtuSn, client.Config); len(dataArray) > 0 {
+							logger.Debug("开始上报平台:%s", dtuSn)
+							reportData(dataArray, client.Config)
+						}
+					}
+				}
+
+				lastMinute = minute
+			}
+		}
+	}
+}
+
+// 更新设备配置
+func updateOnlineConf(msg *dataStruct.DtuRegisterChanMsg) {
+	dtuConfig := &dataStruct.DtuConfig{}
+	if dtu, ok := data.DtuMap.Get(msg.Sn); ok {
+		if err := json.Unmarshal([]byte(dtu.Info.Configs), dtuConfig); err != nil {
+			logger.Error("[%s]设备配置解析失败。", msg.Sn)
+			return
+		}
+		msg.Value.Config = dtuConfig
+		data.OnlineSN.Add(msg.Sn, msg.Value)
+		logger.Debug("[%s]设备配置更新成功。 %v ", msg.Sn, *dtuConfig)
+	} else {
+		logger.Debug("【更新设备配置失败】[%s]设备不存在。", msg.Sn)
+	}
+}

+ 59 - 0
SERVER/Meter_Service/service/reportService/service.go

@@ -0,0 +1,59 @@
+package reportService
+
+import (
+	"net"
+	"time"
+)
+
+// UpToServer 上报数据到平台
+func UpToServer(host, port, date string) (res string, err error) {
+	var (
+		mConn   *net.TCPConn
+		tcpAddr *net.TCPAddr
+	)
+	info := ""
+	res = ""
+	if len(port) > 0 {
+		info = host + ":" + port
+	} else {
+		info = host
+	}
+	writeTimeout := 5 * time.Second
+	readTimeout := 5 * time.Second
+	rxd := make([]byte, 1024)
+	tcpAddr, err = net.ResolveTCPAddr("tcp", info)
+	if err != nil {
+		//fmt.Println("ResolveTCPAddr err")
+		return
+	}
+
+	mConn, err = net.DialTCP("tcp", nil, tcpAddr)
+	if err != nil {
+		return
+	}
+	defer func(mConn *net.TCPConn) {
+		_ = mConn.Close()
+	}(mConn)
+	//fmt.Println("myConn :")
+	err = mConn.SetReadDeadline(time.Now().Add(readTimeout)) // timeout
+	if err != nil {
+		return
+	}
+	err = mConn.SetWriteDeadline(time.Now().Add(writeTimeout)) // timeout
+	if err != nil {
+		return
+	}
+	_, err = mConn.Write([]byte(date))
+	if err != nil {
+		return
+	}
+
+	_, err = mConn.Read(rxd)
+	if err != nil {
+		//tcp send ok but no replay
+		err = nil
+		return
+	}
+	res = string(rxd)
+	return
+}

+ 70 - 0
SERVER/Meter_Service/service/rtuService/rtuTcp.go

@@ -0,0 +1,70 @@
+package rtuService
+
+import (
+	"MeterService/core/logger"
+	"MeterService/core/modbus"
+	"MeterService/core/tcpserver"
+	"MeterService/core/utils"
+	"MeterService/dataStruct"
+	"fmt"
+)
+
+type rtuNetPackager struct {
+	Sn  string
+	Id  int
+	Clt modbus.Client
+	Sta *dataStruct.ClientState
+}
+
+type RtuNetPgr interface {
+	GetHoldingRegs(address, quantity uint16) (adu []byte, err error) // 获取寄存器值
+	SetAddress(Id int)                                               // 设置电表地址
+	SetSerialNumber(sn string)                                       // 设置电表序列号
+	SetClientState(sta *dataStruct.ClientState)                      // 设置客户端
+}
+
+func NewRtuNetPgr(Id byte) RtuNetPgr {
+	handler := modbus.NewRTUClientHandler("")
+	handler.SlaveId = Id
+	return &rtuNetPackager{Sn: "", Id: 1, Clt: modbus.NewClient(handler), Sta: nil}
+}
+
+func (mb *rtuNetPackager) SetAddress(Id int) {
+	if Id > 0 && Id < 255 {
+		mb.Id = Id
+	}
+}
+
+func (mb *rtuNetPackager) SetSerialNumber(sn string) {
+	mb.Sn = sn
+}
+
+func (mb *rtuNetPackager) SetClientState(sta *dataStruct.ClientState) {
+	mb.Sta = sta
+}
+
+// GetHoldingRegs adu []byte, err error
+// 发送modbus rtu报文并解析
+func (mb *rtuNetPackager) GetHoldingRegs(address, quantity uint16) (adu []byte, err error) {
+	var result []byte
+	if mb.Sta == nil {
+		err = fmt.Errorf("客户端不存在")
+		logger.Error("发送失败,%s", err.Error())
+		return
+	}
+	result, err = mb.Clt.PackReadHoldingRegisters(address, quantity)
+	if err != nil {
+		return
+	}
+	res, bok := tcpserver.SendTo(mb.Sta.FD, result)
+	if !bok {
+		if utils.ConnTimeoutJudge(mb.Sta.Times) {
+			logger.Error("发送失败,连接超时")
+		}
+		err = fmt.Errorf("发送失败")
+		return
+	}
+	adu, err = mb.Clt.DePackRegisters(res)
+
+	return
+}

+ 42 - 0
SERVER/Meter_Service/service/webService/middel/middel.go

@@ -0,0 +1,42 @@
+package middel
+
+import (
+	"MeterService/database/appApi"
+	"strconv"
+
+	"github.com/gin-gonic/gin"
+)
+
+func UseCheckApp() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		appIdStr := c.Request.Header.Get("AppId")
+		appSecret := c.Request.Header.Get("Secret")
+		if appIdStr == "" || appSecret == "" {
+			c.AbortWithStatusJSON(401, gin.H{
+				"code": 401,
+				"msg":  "appId和secret不能为空",
+			})
+			return
+		}
+		appId, err := strconv.Atoi(appIdStr)
+		if err != nil {
+			c.AbortWithStatusJSON(401, gin.H{
+				"code": 401,
+				"msg":  "appId必须为数字",
+			})
+			return
+		}
+		dbAppApi := appApi.NewAppApiDb()
+		if !dbAppApi.CheckSecret(&appApi.App{
+			AppId:     appId,
+			AppSecret: appSecret,
+		}) {
+			c.AbortWithStatusJSON(401, gin.H{
+				"code": 401,
+				"msg":  "appId和secret不匹配",
+			})
+			return
+		}
+		c.Next()
+	}
+}

+ 27 - 0
SERVER/Meter_Service/service/webService/router/router.go

@@ -0,0 +1,27 @@
+package router
+
+import (
+	"MeterService/controller"
+	"MeterService/service/webService/middel"
+
+	"github.com/gin-gonic/gin"
+)
+
+func Load(g *gin.Engine) *gin.Engine {
+	g.Use(gin.Recovery())
+	// 注册中间件
+
+	// 注册路由
+	g.GET("/", controller.Index)
+	g.GET("/Device/MockList", controller.Mock)
+
+	app := g.Group("/App")
+	app.POST("/Register", controller.RegisterApp)
+
+	device := g.Group("/Device").Use(middel.UseCheckApp())
+	device.POST("/Add", controller.AddDevice)
+	device.POST("/Update", controller.UpdateDevice)
+	device.POST("/Delete", controller.DeleteDevice)
+
+	return g
+}

+ 29 - 0
SERVER/Meter_Service/service/webService/service.go

@@ -0,0 +1,29 @@
+package webService
+
+import (
+	"MeterService/core/config"
+	"MeterService/core/logger"
+	"MeterService/service/webService/router"
+	"strconv"
+
+	"github.com/gin-gonic/gin"
+)
+
+func NewWebServer() {
+	gin.SetMode(config.C.Vber.Mode)
+
+	g := gin.New()
+	err := g.SetTrustedProxies([]string{"127.0.0.1"})
+	if err != nil {
+		logger.Error("Web服务启动失败: %s", err.Error())
+		return
+	}
+	server := router.Load(gin.New())
+	err = server.Run(":" + strconv.Itoa(config.C.Vber.WebPort))
+	if err != nil {
+		logger.Error("Web服务启动失败: [ %s ]", err.Error())
+		return
+	} else {
+		logger.Debug("Web服务启动成功,端口:%d", config.C.Vber.WebPort)
+	}
+}