go语言计算推算心率算法 http服务

目的

为了计算心率和并且将心率计算作为http服务来运行

几种计算方法

1 基本数据
a) hrv heart rate variability
b) 呼吸
2 傅里叶变换 计算频率 高频和低频
3 隐形马尔科夫 模型 hmm 重在于推测概率
根据最近的心率计算
4 神经网络计算

基本计算

hrv 5 min RR 间期 平均值标准差 sdann
24h 正常的RR间期总体标准差 sdnn
24h 每5分钟时段 标准差的平均值 sdnn index
两个相邻RR间期 差值的均方根RMSSD
24h 相邻两个正常RR 间期差值 大于50ms 的个数百分比pnn50
go语言计算推算心率算法 http服务_第1张图片

go语言计算推算心率算法 http服务_第2张图片

go语言计算推算心率算法 http服务_第3张图片

定义数据结构

type Data struct {
	Heart int
	Time  int64
	//Name  string
}

type Cmd struct {
	// 1 立刻返回,求数据结果立即返回,不用存储
	// 2 单人所有数据24 小时内所有数据
	// 3 其他
	Type int
	//设备号码,唯一标识
	Device string
	Data   []Data
}
type Repay struct {
	Code   int     `json:"code"`
	Device string  `json:"device"`
	Ret    string  `json:"ret"`
	Sdnn   int     `json:"sdnn"`
	Rmssd  int     `json:"rmssd"`
	Pnn50  float32 `json:"pnn50"`
}

声明http 服务

使用http 服务进行数据返回,使用gin 来制作一个server


// 创建一个错误处理函数,避免过多的 if err != nil{} 出现
func dropErr(e error) {
	if e != nil {
		panic(e)
	}
}

/*
 计算一个段落,比如5分钟,或者10分钟,等
*/
func calculate_parag(cmd *Cmd, f int, t int) *Repay {
	size := t - f + 1
	var in []float32 = make([]float32, size)
	var n int = 0
	for i := f; i <= t; i++ {
		in[n] = (float32)(cmd.Data[i].Heart)
		n++
	}

	vsdnn := sdnn.Get_Stddev(in)
	vrmssd := sdnn.Get_Rmssd(in)
	n50 := sdnn.Get_Pnn50(in)

	Repays := &Repay{
		Code:   0,
		Device: cmd.Device,
		Ret:    "正常",
		Sdnn:   int(vsdnn),
		Rmssd:  int(vrmssd),
		Pnn50:  n50,
	}
	return Repays
}

func calculate1(cmd *Cmd, c chan Repay, db *sql.DB) {
	//in1 := []float32{71, 72, 69, 70, 65, 74}
	size := len(cmd.Data)
	fmt.Println("device is ", cmd.Device, " len is ", size)

	if size == 0 {
		return
	}
	var in []float32 = make([]float32, size)

	for i, v := range cmd.Data {
		in[i] = (float32)(v.Heart)
	}

	//ret 为mean均值

	if cmd.Type == 1 {
		fmt.Println("type is 1")
		vsdnn := sdnn.Get_Stddev(in)
		//fmt.Fprintf(w, "%f", vsdnn)
		result := &Repay{
			Code:   0,
			Device: cmd.Device,
			Ret:    "正常",
			Sdnn:   int(vsdnn),
			Rmssd:  0,
			Pnn50:  0.0,
		}
		if vsdnn < 100 {

			if vsdnn < 50 {
				result.Ret = "良好"
			}
		} else {
			result.Ret = "疲劳"
		}
		c <- *result
		//fmt.Println("prepare send")
		//fmt.Fprintf(w, "%f", vsdnn)
		//io.WriteString(w, "hello, world!\n")
		// msg, err := json.Marshal(Repays)
		// if err != nil {
		// 	fmt.Println(err)
		// } else {
		// 	w.Header().Set("content-type", "text/json")
		// 	w.Write(msg)
		// 	fmt.Println(string(msg))
		// }

	} else if cmd.Type == 2 {
		//每5分钟或者10分钟求取一个值,存储,报表所用
		fmt.Println("start to calc")
		var nt int = 0
		var nf int = 0
		tf := cmd.Data[0].Time
		var result *Repay
		for {
			nt++
			if nt == size {
				c <- *result
				break
			}
			if cmd.Data[nt].Time-tf >= 300 { // 大于5分钟 5*60
				result = calculate_parag(cmd, nf, nt)
				fmt.Println(result)
				//记录到数据库
				//计算字符串时间
				time := time.Unix(cmd.Data[nf].Time, 0)
				timestr := calc_time_str(time)
				db_insert(cmd.Device, result.Sdnn, 0, result.Rmssd, result.Pnn50, timestr)
				nf = nt
				tf = cmd.Data[nf].Time

			}

		}

	} else if cmd.Type == 3 {
		fmt.Println("type is 3")
		vsdnn := sdnn.Get_Stddev(in)
		vrmssd := sdnn.Get_Rmssd(in)
		n50 := sdnn.Get_Pnn50(in)
		//记录到数据库
		result := &Repay{
			Code:   0,
			Device: cmd.Device,
			Ret:    "",
			Sdnn:   int(vsdnn),
			Rmssd:  int(vrmssd),
			Pnn50:  n50,
		}
		c <- *result
		//s := fmt.Sprintf(`{"sdnn":%f,"rmssd":%f,"pnn50":%f}`, vsdnn, vrmssd, n50)

		//msg, _ := json.Marshal(Repays)
		//fmt.Println(string(msg))
		//w.Header().Set("content-type", "application/json;charset=UTF-8")
		//w.Write(msg)
	}

	//
}
func init() {
	//s := fmt.Sprintf(`{"sdnn":%f,"rmssd":%f,"pnn50":%f}`, 1.0, 2.0, 3.0)
	//fmt.Println(s)
}

func gin_server() {
	r := gin.Default()

	r.GET("/api/device/:name", func(c *gin.Context) {
		dname := c.Param("name")
		c.JSON(200, gin.H{
			"message": dname,
		})
	})

	r.POST("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "POST",
		})
	})
	r.Run(":9000")
}

func main() {
	defer dbclose()
	config := config_init()

	storage_init()
	go gin_server()
	http.HandleFunc("/hrv", func(w http.ResponseWriter, r *http.Request) {

		fmt.Println(html.EscapeString(r.URL.Path))

		if r.Method == "POST" {
			b, err := ioutil.ReadAll(r.Body)
			if err != nil {
				log.Println("Read failed:", err)
			}
			//defer r.Body.Close()

			cmd := &Cmd{}
			err = json.Unmarshal(b, cmd)
			if err != nil {
				log.Println("json format error:", err)
			} else {
				//c := make(chan interface{}, 1)
				c := make(chan Repay, 1)
				go calculate1(cmd, c, db)

				select {
				//case <-ctx.Done():
				//	log.Printf("Context interrupt or timeout: %v\n", ctx.Err())

				case result := <-c:
					msg, _ := json.Marshal(result)
					fmt.Println(string(msg))
					w.Header().Set("content-type", "application/json;charset=UTF-8")
					w.Write(msg)
				}
				// log.Println("cmd:", string(b))
				// log.Println("cmd:", cmd.Data[0])
				//io.WriteString(w, "hello, world!\n")
				//w.ResponseWriter("1")
				//time.Sleep(1 * time.Second)
				//fmt.Fprintf(w, "%f", 100.0)
			}

		} else {

			log.Println("not support")

			w.WriteHeader(405)
			return
		}

	})

	serveraddress := ":" + config.Port
	fmt.Println("server lister at ", serveraddress)
	log.Fatal(http.ListenAndServe(serveraddress, nil))

}

以上的服务比较简单,不做过多解释,以下贴出使用sqlite的代码sqlite.go,go语言使用sqlite是要用mingw来编译sqlite的,这个注意以下,我就直接贴代码了

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"unsafe"
	_ "github.com/mattn/go-sqlite3"
)

var db *sql.DB
var err error
var sql_table = `CREATE TABLE if not exists "data_1" (
    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
    "deviceid" VARCHAR(64) NULL,
    "sdnn" INTEGER,
	"average" INTEGER,
    "rmssd" INTEGER,
	"pnn50" float,
    "timestart" TIMESTAMP default (datetime('now', 'localtime'))
);
CREATE INDEX deviceid_idx ON file_hash_list (deviceid);
`

type Data0 struct {
	id        int
	Deviceid  string
	Sdnn      int
	Average   int
	Rmssd     int
	Pnn50     float32
	Timestart string
}

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

func db_insert(did string, ssdn int, average int, rmssd int, pnn50 float32, timestart string) int64 {
	stmt, err := db.Prepare("INSERT INTO data_1(deviceid, sdnn,average,rmssd,pnn50,timestart) values(?,?,?,?,?,?)")
	checkErr(err)

	res, err := stmt.Exec(did, ssdn, average, rmssd, pnn50, timestart)
	checkErr(err)

	id, err := res.LastInsertId()
	checkErr(err)

	return id

}
func db_update_average( /*db *sql.DB,*/ deviceid string, average int) int64 {

	stmt, err := db.Prepare("update data_1 set deviceid=? where deviceid=?")
	checkErr(err)

	res, err := stmt.Exec(average, deviceid)
	checkErr(err)

	affect, err := res.RowsAffected()
	checkErr(err)
	return affect
}

func db_select_all( /*db *sql.DB,*/ data0 *Data0) {
	rows, err := db.Query("SELECT * FROM data_1")

	checkErr(err)

	for rows.Next() {
		err = rows.Scan(&data0.id, &data0.Deviceid, &data0.Sdnn, &data0.Average,
			&data0.Rmssd, &data0.Pnn50, &data0.Timestart)
		checkErr(err)
	}
}
func getJSON(sqlString string) (string, error) {
	stmt, err := db.Prepare(sqlString)
	if err != nil {
		return "", err
	}
	defer stmt.Close()
	rows, err := stmt.Query()
	if err != nil {
		return "", err
	}
	defer rows.Close()
	columns, err := rows.Columns()
	if err != nil {
		return "", err
	}
	count := len(columns)
	tableData := make([]map[string]interface{}, 0)
	values := make([]interface{}, count)
	valuePtrs := make([]interface{}, count)
	for rows.Next() {
		for i := 0; i < count; i++ {
			valuePtrs[i] = &values[i]
		}
		rows.Scan(valuePtrs...)
		entry := make(map[string]interface{})
		for i, col := range columns {
			var v interface{}
			val := values[i]
			b, ok := val.([]byte)
			if ok {
				v = string(b)
			} else {
				v = val
			}
			entry[col] = v
		}
		tableData = append(tableData, entry)
	}
	jsonData, err := json.Marshal(tableData)
	if err != nil {
		return "", err
	}
	s1 := (*string)(unsafe.Pointer(&jsonData))
	return *s1, nil
	//return string(jsonData), nil
}

//size:每页显示条数,index页码
func db_select_by(tablename string, deviceid string, size int, index int) {
	//select * from GuestInfo order by GuestId limit {0} offset {0}*{1}", size, index-1);
	s := fmt.Sprintf("select * from %s where deviceid='%s' order by id desc limit %d offset %d",
		tablename, deviceid, size, size*(index-1))
	fmt.Println("sql is:", s)
	ret, err := getJSON(s)
	if err != nil {

	} else {
		//http 返回数据
		fmt.Println(ret)
	}
}

func db_delete_data(id int) int64 {
	stmt, err := db.Prepare("delete from data_1 where id=?")
	checkErr(err)

	res, err1 := stmt.Exec(id)
	checkErr(err1)

	affect, err2 := res.RowsAffected()
	checkErr(err2)

	return affect

}

func init() {
	fmt.Println("sqlite open")
	db, err = sql.Open("sqlite3", "./data.db")
	checkErr(err)
	db.Exec(sql_table) //执行数据表
	//defer db.Close()
}

func dbclose() {
	db.Close()
}

//just for unit test
func sqlite_test() {

	// db, err = sql.Open("sqlite3", "./data.db")
	// defer db.Close()
	// checkErr(err)
	// db.Exec(sql_table) //执行数据表
	// id := db_insert("qianbo", 15, 70, 20, 0.2, "2021-01-09 07:05:22")
	// fmt.Println("insert:", id)
	// id = db_insert("guanzhi", 16, 71, 25, 0.3, "2021-12-09 07:05:22")
	// fmt.Println("insert:", id)
	// affected := db_update_average("qianbo", 67)
	// fmt.Println("update:", affected)

	// var data0 Data0
	// db_select_all(&data0)
	// fmt.Println(id, data0.Deviceid)
	//'select * from data_1 where deviceid='qianbo' order by id desc limit 5 offset 0

	db_select_by("data_1", "qianbo", 5, 1)
	db_select_by("data_1", "qianbo", 5, 2)
}

配置文件

配置文件主要是用来配置端口和配置提交数据地址
config.yaml

port: 8080
postaddress: "http://127.0.0.1/qianbo"


package main

import (
	"fmt"

	"github.com/spf13/viper"
)

type Config struct {
	Port        string
	PostAddress string
}

func config_init() *Config {
	//监听事件

	viper.SetConfigName("config")
	viper.SetConfigType("yaml")
	viper.AddConfigPath(".")
	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}

	config := &Config{}

	config.Port = viper.GetString("port")
	config.PostAddress = viper.GetString("postaddress")
	fmt.Println(config.Port, config.PostAddress)
	viper.WatchConfig()
	return config
	// gin.SetMode(gin.ReleaseMode)
	// //r := gin.Default()
	// if err := r.Run(fmt.Sprintf(":%d", viper.Get("port"))); err != nil {
	// 	panic(err)
	// }
}

算法

最后给出几个算法的计算方法,确实比较简单,直接看代码就行了

package sdnn

import (
	"fmt"
	"math"
)

func Print(arr []int) {
	for i, v := range arr {
		fmt.Printf("arr[%d]=%d\n", i, v)
	}
}
func Get_Average(arr []float32) float32 {
	var x float32 = 0.
	for _, v := range arr {
		//fmt.Printf("arr[%d]=%f\n", i, v)
		a := (60 * 1000) / v
		x += a
	}
	var ret float32 = 0.
	ret = x / (float32)(len(arr))
	return ret
}

//五分钟就是sdann
//24销售是sdnn
func Get_Stddev(arr []float32) float64 {
	var mean float32 = Get_Average(arr)
	var x float32 = 0.0
	for _, v := range arr {
		a := 60000 / v
		x += (a - mean) * (a - mean)
	}
	var size float64 = (float64)(len(arr))
	var ret float64 = math.Sqrt((float64)(x) / size)
	return ret
}
func Get_Rmssd(arr []float32) float64 {
	var x float32 = 0.0
	for i := 1; i < len(arr); i++ {
		a := 60000 / arr[i-1]
		b := 60000 / arr[i]
		x += (b - a) * (b - a)
	}
	var size float64 = (float64)(len(arr) - 1)
	var ret float64 = math.Sqrt((float64)(x) / size)
	return ret
}

//24h 相邻两个正常RR 间期差值 大于50ms 的个数百分比pnn50
//迷走神经
func Get_Pnn50(arr []float32) float32 {
	var pnn int = 0
	for i := 1; i < len(arr); i++ {
		a := 60000 / arr[i-1]
		b := 60000 / arr[i]
		if a > b {
			if (a - b) > 50 {
				pnn++
			}
		} else {
			if (b - a) > 50 {
				pnn++
			}
		}

	}
	var size float32 = (float32)(len(arr) - 1)
	return (float32)((float32)(pnn) / size)
}

post 测试数据

为了编写测试程序而写,当然最好使用python来做,不过go也可以了

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"time"
)

type Data struct {
	Heart int
	Time  int
	Name  string
}
type Cmd struct {
	Type int
	Data []Data
}

var g_str = `{"Type":2,"Device":"qianbo","Data":[{"Heart":78,"Time":678},
{"Heart":75,"Time":700},
{"Heart":69,"Time":800},
{"Heart":72,"Time":900},
{"Heart":73,"Time":1000},
{"Heart":70,"Time":1100},
{"Heart":73,"Time":1200},
{"Heart":73,"Time":1300},
{"Heart":67,"Time":1400},
{"Heart":68,"Time":1500},
{"Heart":70,"Time":1600},
{"Heart":70,"Time":1700},
{"Heart":71,"Time":1750},
{"Heart":68,"Time":1850},
{"Heart":67,"Time":1900},
{"Heart":65,"Time":1950},
{"Heart":73,"Time":2000},
{"Heart":70,"Time":2050},
{"Heart":69,"Time":2100}
]}`

var g_url = "http://127.0.0.1:8080/hrv"
var g_jsonStr = []byte(g_str)

func GetPostResponse(url, bodyType string, body *[]byte) (rdata []byte, err error) {
	b := bytes.NewBuffer(*body)
	var r *http.Response
	r, err = http.Post(url, bodyType, b)

	if err == nil {
		rbody := (*r).Body
		defer rbody.Close()

		var nRead int
		nRead, err = rbody.Read(rdata)
		if err != nil {
			fmt.Printf("GetPostResponse from (%s), read data error.", url)
			fmt.Println(err.Error())
		}
		if nRead <= 0 {
			err = fmt.Errorf("GetPostResponse from (%s), read data error (%d)", url, nRead)
			fmt.Println(err.Error())
		}
	} else {
		fmt.Printf("GetPostResponse from (%s), get error.", url)
		fmt.Println(err.Error())
	}

	return rdata, err
}

func SamplePost() {
	reader := bytes.NewReader(g_jsonStr)

	request, err := http.NewRequest("POST", g_url, reader)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	request.Header.Set("Content-Type", "application/json;charset=UTF-8")
	client := http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	respBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	//byte数组直接转成string,优化内存
	fmt.Println(string(respBytes))
	// str := (*string)(unsafe.Pointer(&respBytes))
	// fmt.Println(*str)
}

func main() {
	SamplePost()
}

func main2() {
	rdata, err := GetPostResponse(g_url, "application/json", &g_jsonStr)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(len(rdata))
		fmt.Println(string(rdata))
	}
}

func main_() {
	//var arr [2]Data
	//json.Unmarshal([]byte(str), &s)

	// data := make(map[string]interface{})
	// data["Name"] = "qianbo"
	// data["Heart"] = 67
	// data["Time"] = 778

	// D := make(map[string]interface{})
	// D["Type"] = 1
	// D["Data"] = data
	// bytesData, err := json.Marshal(D)

	// if err != nil {
	// 	fmt.Println(err.Error())
	// 	return
	// }
	// reader := bytes.NewReader(bytesData)
	//request, err := http.NewRequest("POST", url, reader)

	client := http.Client{
		Transport: &http.Transport{
			Dial: func(netw, addr string) (net.Conn, error) {
				conn, err := net.DialTimeout(netw, addr, time.Second*5) //设置建立连接超时
				if err != nil {
					return nil, err
				}
				conn.SetDeadline(time.Now().Add(time.Second * 5)) //设置发送接受数据超时
				return conn, nil
			},
			ResponseHeaderTimeout: time.Second * 5,
		},
	}

	request, err := http.NewRequest("POST", g_url, bytes.NewBuffer(g_jsonStr))
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	request.Header.Set("Content-Type", "application/json;charset=UTF-8")
	request.Header.Set("Connection", "Keep-Alive")
	//request.Header.Set("Cookie", "name=anny")

	fmt.Println("send")
	resp, err := client.Do(request)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer resp.Body.Close()
	fmt.Println("recv")
	body, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		fmt.Println(err.Error())
		return
	}
	//byte数组直接转成string
	//strret := (*string)(unsafe.Pointer(&resp.Body))
	fmt.Println(len(body))
	fmt.Println(string(body))

}

缓存数据如何存储

下面是为了缓存数据而编写的入到redis里面而写,不一定要用

package main

import (
	"context"
	"fmt"
	"log"

	//"github.com/go-redis/redis"
	"github.com/go-redis/redis/v8"
)

var rdb *redis.Client
var ctx = context.Background()

// // 初始化连接
func storage_init() (err error) {

	rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})

	_, err = rdb.Ping(ctx).Result()
	if err != nil {
		fmt.Println("error connect redis")
		return err
	}
	return nil
}
func get(key string) string {
	val, err := rdb.Get(ctx, key).Result()
	if err == redis.Nil {
		fmt.Println("key of ", key, "does not exists")
		return ""
	} else if err != nil {
		return ""
		//panic(err)
	} else {
		fmt.Println(key, val)
		return val
	}
}
func set(key string, value float32) {

	err := rdb.Set(ctx, key, value, 0).Err()
	if err != nil {
		panic(err)
	}
}
func hset(hashTable, key, val string) {
	isSetSuccessful, err := rdb.HSet(ctx, hashTable, key, val).Result()

	if err != nil {
		log.Fatal(err)
	}
	//如果键存在这返回false,如果键不存在则返回true
	fmt.Println(isSetSuccessful)
}

//redis命令:hget hashTable key
func hget(hashTable, key string) {
	val, err := rdb.HGet(ctx, hashTable, key).Result()

	if err != nil {
		log.Fatal(err)
	} else {
		fmt.Println(val)
	}
	// for k, v := range val {
	// 	fmt.Printf("k = %v v = %s\n", k, v)
	// }
}
func hgetall(hashTable string) {
	val, err := rdb.HGetAll(ctx, hashTable).Result()

	if err != nil {
		log.Fatal(err)
	} else {
		fmt.Println(val)
	}
	// for k, v := range val {
	// 	fmt.Printf("k = %v v = %s\n", k, v)
	// }
}

func main__() {
	storage_init()
	set("qianbo", 1000)
	get("qianbo")
	hset("device001", "1001", "{abc:100,d:23}")
	hset("device001", "1002", "{abc:101,d:123}")
	hset("device001", "1003", "{abc:102,d:223}")
	hget("device001", "1001")
	hget("device001", "1002")
	hget("device001", "1003")
	hgetall("device001")
}

时间测试

其中为了测试时间差也写了一些测试,一并贴出

package main

import (
	"fmt"
	"time"
)

func calc_seconds(t1 string, t2 string) int {

	f1, err := time.Parse("2006-01-02 15:04:05", t1)
	if err != nil {
		return -1
	}

	f2, err2 := time.Parse("2006-01-02 15:04:05", t2)
	if err2 != nil {
		return -1
	}
	d := (int)(f2.Sub(f1).Seconds())
	if d < 0 {
		x := 0 - d
		return x
	}
	return d
}
func calc_timenow() string {

	timeStr := time.Now().Format("2006-01-02 15:04:05")
	//fmt.Println(timeStr)
	return timeStr
}
func calc_time_str(t time.Time) string {
	return t.Format("2006-01-02 15:04:05")
}
func test_time() {
	timestamp := time.Now().Unix()
	fmt.Println(timestamp)
	x := time.Unix(timestamp, 0)
	fmt.Println(x)
	//获取时间戳
	fmt.Println(calc_timenow())
	f1 := "2021-04-11 13:34:37"
	f2 := "2021-04-11 13:34:30"
	fmt.Println(calc_seconds(f1, f2))

}

最后贴出go.mod 文件

module hrv

go 1.15

require (
	github.com/garyburd/redigo v1.6.3
	github.com/gin-gonic/gin v1.7.7
	github.com/go-redis/redis v6.15.9+incompatible
	github.com/go-redis/redis/v8 v8.11.4
	github.com/mattn/go-sqlite3 v1.14.10
	github.com/spf13/viper v1.10.1
)

以上是为了计算心率数据而做的非产品代码,读者可做参考

你可能感兴趣的:(go,http协议,http,golang,算法,http)