目的
为了计算心率和并且将心率计算作为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
定义数据结构
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)
vpnn50 := sdnn.Get_Pnn50(in)
Repays := &Repay{
Code: 0,
Device: cmd.Device,
Ret: "正常",
Sdnn: int(vsdnn),
Rmssd: int(vrmssd),
Pnn50: vpnn50,
}
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)
vpnn50 := sdnn.Get_Pnn50(in)
//记录到数据库
result := &Repay{
Code: 0,
Device: cmd.Device,
Ret: "",
Sdnn: int(vsdnn),
Rmssd: int(vrmssd),
Pnn50: vpnn50,
}
c <- *result
//s := fmt.Sprintf(`{"sdnn":%f,"rmssd":%f,"pnn50":%f}`, vsdnn, vrmssd, vpnn50)
//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
)
以上是为了计算心率数据而做的非产品代码,读者可做参考