Add files via upload

This commit is contained in:
dije07
2024-04-01 16:31:24 +07:00
committed by GitHub
parent e1c8cb365f
commit 0193e344c7
17 changed files with 1751 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
## Docker Testing
version: "3.7"
services:
mqtt-broker:
image: eclipse-mosquitto
container_name: mqtt-broker
ports:
- "1883:1883" #default mqtt port
- "9001:9001" #default mqtt port for websockets
volumes:
- ./mqtt/config:/mosquitto/config
- ./mqtt/data:/mosquitto/data
- ./mqtt/log:/mosquitto/log/mosquitto.log
mqtt-client:
image: emqx/mqttx-web
container_name: mqtt-client
ports:
- "9000:80"
db:
container_name: db
image: postgres:14.1-alpine
restart: always
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- '5432:5432'
volumes:
- db:/var/lib/postgresql/data
volumes:
config:
data:
log:
db:
networks:
default:
name: mqtt
external: true

View File

@@ -0,0 +1,37 @@
module github.com/stanleydv12/gateway-classification
go 1.21.3
require (
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e
github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99
github.com/joho/godotenv v1.5.1
github.com/mitchellh/mapstructure v1.5.0
)
require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/ianlancetaylor/demangle v0.0.0-20240205174729-1f824a1a9b87 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/tensorflow/tensorflow v2.15.0+incompatible // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
)
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.5
)

View File

@@ -0,0 +1,69 @@
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e h1:9+2AEFZymTi25FIIcDwuzcOPH04z9+fV6XeLiGORPDI=
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e/go.mod h1:TelZuq26kz2jysARBwOrTv16629hyUsHmIoj54QqyFo=
github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99 h1:8Bt1P/zy1gb37L4n8CGgp1qmFwBV5729kxVfj0sqhJk=
github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/ianlancetaylor/demangle v0.0.0-20240205174729-1f824a1a9b87 h1:IG+58MeF0K/wSXknV4FMOABt6B4VZCguRDxYSCJqWb4=
github.com/ianlancetaylor/demangle v0.0.0-20240205174729-1f824a1a9b87/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
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.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/tensorflow/tensorflow v2.15.0+incompatible h1:qWaan+jC2rrHctJliI+TSAwLGdCoQdsrwN7YddLv81Y=
github.com/tensorflow/tensorflow v2.15.0+incompatible/go.mod h1:itOSERT4trABok4UOoG+X4BoKds9F3rIsySdn+Lvu90=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=

View File

@@ -0,0 +1,28 @@
package main
import (
"github.com/stanleydv12/gateway-classification/src/database"
"github.com/stanleydv12/gateway-classification/src/handler"
"github.com/stanleydv12/gateway-classification/src/mqtt"
)
// main initializes the database and MQTT setup, then starts listening for MQTT messages.
//
// No parameters.
// No return types.
func main() {
// Setup Database
db := database.SetupDatabase()
handler.SetDBInstance(db)
// Setup Mqtt
mqtt.SetupMqtt()
// Application set to listen
mqtt.Sub(mqtt.Client)
handler.StartTimer()
// Block the main function with a select statement
select {}
}

View File

@@ -0,0 +1,7 @@
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
allow_anonymous true
listener 1883 0.0.0.0
listener 9001 0.0.0.0
protocol websockets

View File

@@ -0,0 +1,27 @@
# Panduan Setup MQTT
Berikut adalah langkah-langkah untuk melakukan setup pada MQTT:
1. Buatlah folder mqtt yang didalamnya terdapat 3 folder lain yaitu config, data, dan log.
2. Lalu konfigurasi mqtt sesuai kebutuhan pada folder mqtt -> config -> mosquitto.conf
# Panduan Menjalankan Dockerfile
Berikut adalah langkah-langkah untuk menjalankan Dockerfile dan membangun image Docker:
## Langkah 1: Build Docker Image
```bash
docker build -t myapp .
```
## Langkah 2: Kirim Image ke Registry Cloud
```bash
docker login <registry-url>
docker tag <image-name>:<tag> <registry-url>/<username>/<image-name>:<tag>
```
# Panduan Testing
Berikut adalah langkah-langkah untuk melakukan testing menggunakan JMeter:
1. Tambahkan plugin MQTT yang terdapat di folder infrastructure pada JMeter.
2. Lalu open file dengan tipe .jmx pada folder infrastructure tersebut.

View File

@@ -0,0 +1,112 @@
package classify
import (
"fmt"
"math"
)
type Complex struct {
Re float64 // the real part
Im float64 // the imaginary part
}
// create a new object with the given real and imaginary parts
func NewComplex(real, imag float64) Complex {
return Complex{Re: real, Im: imag}
}
// return a string representation of the invoking Complex object
func (c Complex) String() string {
if c.Im == 0 {
return fmt.Sprintf("%g", c.Re)
}
if c.Re == 0 {
return fmt.Sprintf("%gi", c.Im)
}
if c.Im < 0 {
return fmt.Sprintf("%g - %gi", c.Re, -c.Im)
}
return fmt.Sprintf("%g + %gi", c.Re, c.Im)
}
// return abs/modulus/magnitude
func (c Complex) Abs() float64 {
return math.Hypot(c.Re, c.Im)
}
// return a new Complex object whose value is (this + b)
func (c Complex) Plus(b Complex) Complex {
return Complex{c.Re + b.Re, c.Im + b.Im}
}
// return a new Complex object whose value is (this - b)
func (c Complex) Minus(b Complex) Complex {
return Complex{c.Re - b.Re, c.Im - b.Im}
}
// return a new Complex object whose value is (this * b)
func (c Complex) Times(b Complex) Complex {
return Complex{c.Re*b.Re - c.Im*b.Im, c.Re*b.Im + c.Im*b.Re}
}
// return a new Complex object whose value is the reciprocal of this
func (c Complex) Reciprocal() Complex {
scale := c.Re*c.Re + c.Im*c.Im
return Complex{c.Re / scale, -c.Im / scale}
}
// return a / b
func (c Complex) Divides(b Complex) Complex {
return c.Times(b.Reciprocal())
}
// return a new Complex object whose value is the complex sine of this
func (c Complex) Sin() Complex {
return Complex{math.Sin(c.Re) * math.Cosh(c.Im), math.Cos(c.Re) * math.Sinh(c.Im)}
}
// return a new Complex object whose value is the complex cosine of this
func (c Complex) Cos() Complex {
return Complex{math.Cos(c.Re) * math.Cosh(c.Im), -math.Sin(c.Re) * math.Sinh(c.Im)}
}
// equals returns true if the given Complex object is equal to the receiver
func (c Complex) Equals(x Complex) bool {
return c.Re == x.Re && c.Im == x.Im
}
// FFT computes the FFT of a complex sequence x[] of length n.
func FFT(x []Complex) []Complex {
n := len(x)
if n == 1 {
return []Complex{x[0]}
}
if n%2 != 0 {
panic("n is not a power of 2")
}
// Compute FFT of even terms
even := make([]Complex, n/2)
for k := 0; k < n/2; k++ {
even[k] = x[2*k]
}
q := FFT(even)
// Compute FFT of odd terms
odd := even // Reuse the array
for k := 0; k < n/2; k++ {
odd[k] = x[2*k+1]
}
r := FFT(odd)
// Combine
y := make([]Complex, n)
for k := 0; k < n/2; k++ {
kth := -2 * math.Pi * float64(k) / float64(n)
wk := Complex{math.Cos(kth), math.Sin(kth)}
y[k] = q[k].Plus(wk.Times(r[k]))
y[k+n/2] = q[k].Minus(wk.Times(r[k]))
}
return y
}

View File

@@ -0,0 +1 @@
package classify

View File

@@ -0,0 +1,96 @@
package classify
import (
"errors"
"io/ioutil"
"strconv"
"strings"
)
type RealMatrix struct {
Rows int
Cols int
Data [][]float64
}
type Weight struct {
W RealMatrix
BW RealMatrix
}
type ELMModel struct {
InputWeight RealMatrix
BiasInputWeight RealMatrix
OutputWeight RealMatrix
Separator string
}
func NewELMModel(inputWeightFilePath, outputWeightFilePath string) (*ELMModel, error) {
var elmModel ELMModel
inputWeightFileBytes, err := ioutil.ReadFile(inputWeightFilePath)
if err != nil {
return nil, err
}
outputWeightFileBytes, err := ioutil.ReadFile(outputWeightFilePath)
if err != nil {
return nil, err
}
inputWeightFileLines := strings.Split(string(inputWeightFileBytes), "\n")
outputWeightFileLines := strings.Split(string(outputWeightFileBytes), "\n")
weight, err := convertListCsvTo2dArr(inputWeightFileLines, true)
if err != nil {
return nil, err
}
elmModel.InputWeight = weight.W
elmModel.BiasInputWeight = weight.BW
weight, err = convertListCsvTo2dArr(outputWeightFileLines, false)
if err != nil {
return nil, err
}
elmModel.OutputWeight = weight.W
return &elmModel, nil
}
func convertListCsvTo2dArr(input []string, useBias bool) (Weight, error) {
var weight Weight
listSize := len(input)
if listSize == 0 {
return weight, errors.New("empty input list")
}
csvElementSize := len(strings.Split(input[0], ","))
for _, line := range input {
if len(strings.Split(line, ",")) != csvElementSize {
return weight, errors.New("invalid CSV length")
}
}
weightData := make([][]float64, listSize)
biasWeightData := make([][]float64, listSize)
for i, line := range input {
splittedLine := strings.Split(line, ",")
temp := make([]float64, len(splittedLine))
for j, val := range splittedLine {
temp[j], _ = strconv.ParseFloat(strings.TrimSpace(val), 64)
}
if useBias {
weightData[i] = temp[:len(temp)-1]
biasWeightData[i] = []float64{temp[len(temp)-1]}
} else {
weightData[i] = temp
}
}
weight.W = RealMatrix{Rows: listSize, Cols: len(weightData[0]), Data: weightData}
weight.BW = RealMatrix{Rows: listSize, Cols: 1, Data: biasWeightData}
return weight, nil
}

View File

@@ -0,0 +1,255 @@
package classify
import (
"math"
"sort"
)
type HRVFeature struct {
F01_AVNN float64
F02_SDNN float64
F03_RMSSD float64
F04_SDSD float64
F05_NNx float64
F06_PNNx float64
F07_HRV_TRIANGULAR_IDX float64
F08_SD1 float64
F09_SD2 float64
F10_SD1_SD2_RATIO float64
F11_S float64
F12_TP float64
F13_pLF float64
F14_pHF float64
F15_LFHFratio float64
F16_VLF float64
F17_LF float64
F18_HF float64
}
func NewHRVFeature(rrIntervalSet RRIntervalSet) *HRVFeature {
var hrv HRVFeature
rrIntervalValue := rrIntervalSet.RRIntervalValue
rrIntervalsValueDiff := rrIntervalSet.RRIntervalsValueDiff
hrv.F01_AVNN = f01_AVNN(rrIntervalValue)
hrv.F02_SDNN = f02_SDNN(rrIntervalValue)
hrv.F03_RMSSD = f03_RMSSD(rrIntervalsValueDiff)
hrv.F04_SDSD = f04_SDSD(rrIntervalsValueDiff)
hrv.F05_NNx = f05_NNx(rrIntervalsValueDiff, 50)
hrv.F06_PNNx = f06_PNNx(rrIntervalValue, hrv.F05_NNx)
hrv.F07_HRV_TRIANGULAR_IDX = f07_HRV_TRIANGULAR_IDX(rrIntervalValue)
hrv.F08_SD1 = f08_SD1(hrv.F04_SDSD)
hrv.F09_SD2 = f09_SD2(hrv.F02_SDNN, hrv.F04_SDSD)
hrv.F10_SD1_SD2_RATIO = f10_SD1_SD2_RATIO(hrv.F08_SD1, hrv.F09_SD2)
hrv.F11_S = f11_S(hrv.F08_SD1, hrv.F09_SD2)
feature12To18 := f12_18(rrIntervalValue, 2)
hrv.F12_TP = feature12To18.TP
hrv.F13_pLF = feature12To18.pLF
hrv.F14_pHF = feature12To18.pHF
hrv.F15_LFHFratio = feature12To18.LFHFratio
hrv.F16_VLF = feature12To18.VLF
hrv.F17_LF = feature12To18.LF
hrv.F18_HF = feature12To18.HF
return &hrv
}
func f01_AVNN(rrIntervalValue []float64) float64 {
return mean(rrIntervalValue)
}
func f02_SDNN(rrIntervalValue []float64) float64 {
return sampleStandardDeviation(rrIntervalValue)
}
func f03_RMSSD(rrIntervalsValueDiff []float64) float64 {
return math.Sqrt(mean(powList(rrIntervalsValueDiff, 2)))
}
func f04_SDSD(rrIntervalsValueDiff []float64) float64 {
return sampleStandardDeviation(rrIntervalsValueDiff)
}
func f05_NNx(rrIntervalsValueDiff []float64, x float64) float64 {
count := filterCount(mulList(rrIntervalsValueDiff, 1000), func(y float64) bool {
return y > x
})
return float64(count)
}
func f06_PNNx(rrIntervalValue []float64, NNx float64) float64 {
return (NNx / (float64(len(rrIntervalValue)) - 1)) * 100
}
func f07_HRV_TRIANGULAR_IDX(rrIntervalValue []float64) float64 {
binSize := 7.812
var tempRr []float64
for _, val := range rrIntervalValue {
tempRr = append(tempRr, val*1000)
}
sort.Float64s(tempRr)
maxVal := tempRr[len(tempRr)-1]
minVal := tempRr[0]
binCount := math.Ceil((maxVal - minVal) / binSize)
edges := make([]float64, int(binCount)+1)
var Nds []float64
edges[0] = minVal
for i := 1; i <= int(binCount); i++ {
edges[i] = edges[i-1] + binSize
var d float64
for _, x := range tempRr {
if x >= edges[i-1] && x < edges[i] {
d++
}
}
if d != 0 {
Nds = append(Nds, d)
}
}
return max(Nds) / sum(Nds)
}
func f08_SD1(sdsd float64) float64 {
return math.Sqrt(math.Pow(sdsd, 2) / 2)
}
func f09_SD2(sdnn, sdsd float64) float64 {
return math.Sqrt(2*math.Pow(sdnn, 2) - math.Pow(sdsd, 2)/2)
}
func f10_SD1_SD2_RATIO(sd1, sd2 float64) float64 {
return sd1 / sd2
}
func f11_S(sd1, sd2 float64) float64 {
return math.Pi * sd1 * sd2
}
func f12_18(rrIntervalValue []float64, Fs float64) Feature12To18 {
var feature Feature12To18
// Implement the logic for feature calculation
return feature
}
func filterF(YY []float64, f []float64, predicate func(float64) bool) []float64 {
var result []float64
for i, val := range f {
if predicate(val) {
result = append(result, YY[i])
}
}
return result
}
func nanzscore(input []float64) []float64 {
m := nanmean(input)
s := nanstd(input)
var z []float64
for _, val := range input {
z = append(z, (val-m)/s)
}
return z
}
func nanmean(input []float64) float64 {
return mean(input)
}
func nanstd(input []float64) float64 {
return populationStandardDeviation(input)
}
type Feature12To18 struct {
TP float64
pLF float64
pHF float64
LFHFratio float64
VLF float64
LF float64
HF float64
}
type RRIntervalSet struct {
RRIntervalValue []float64
RRIntervalsValueDiff []float64
}
func mean(arr []float64) float64 {
sum := 0.0
for _, val := range arr {
sum += val
}
return sum / float64(len(arr))
}
func sampleStandardDeviation(arr []float64) float64 {
mean := mean(arr)
variance := 0.0
for _, val := range arr {
variance += math.Pow(val-mean, 2)
}
return math.Sqrt(variance / float64(len(arr)-1))
}
func powList(arr []float64, exp float64) []float64 {
var result []float64
for _, val := range arr {
result = append(result, math.Pow(val, exp))
}
return result
}
func filterCount(arr []float64, predicate func(float64) bool) int {
count := 0
for _, val := range arr {
if predicate(val) {
count++
}
}
return count
}
func sum(arr []float64) float64 {
total := 0.0
for _, val := range arr {
total += val
}
return total
}
func max(arr []float64) float64 {
if len(arr) == 0 {
return 0
}
max := arr[0]
for _, val := range arr {
if val > max {
max = val
}
}
return max
}
func populationStandardDeviation(input []float64) float64 {
meanVal := mean(input)
variance := 0.0
for _, val := range input {
variance += math.Pow(val-meanVal, 2)
}
return math.Sqrt(variance / float64(len(input)))
}
func mulList(input []float64, multiplier float64) []float64 {
result := make([]float64, len(input))
for i, val := range input {
result[i] = val * multiplier
}
return result
}

View File

@@ -0,0 +1,111 @@
package database
import (
"fmt"
"log"
"os"
"time"
"github.com/joho/godotenv"
"github.com/stanleydv12/gateway-classification/src/entity"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// SetupDatabase initializes and returns a *gorm.DB object representing a connection to the database.
//
// It reads the database configuration from the environment variables and establishes a connection to the database using the specified parameters.
// The function automatically migrates the entity.SensorData struct to the database and returns the *gorm.DB object.
func SetupDatabase() *gorm.DB {
var err error
errEnv := godotenv.Load(".env")
if errEnv != nil {
log.Fatal("Error loading .env")
}
host := os.Getenv("DSN_HOST")
user := os.Getenv("DSN_USER")
password := os.Getenv("DSN_PASSWORD")
dbname := os.Getenv("DSN_DB_NAME")
port := os.Getenv("DSN_PORT")
sslMode := os.Getenv("SSL_MODE")
timeZone := os.Getenv("TIMEZONE")
dsn :=
"host=" + host +
" user=" + user +
" password=" + password +
" dbname=" + dbname +
" port=" + port +
" sslmode=" + sslMode +
" TimeZone=" + timeZone
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("Error connecting to the database: %v", err)
}
err = AutoMigrateAllEntities(db)
if err != nil {
log.Fatalf("Error migrating entities: %v", err)
}
// Seeding
InsertTestData(db)
return db
}
// AutoMigrateAllEntities migrates all entities to the database.
func AutoMigrateAllEntities(db *gorm.DB) error {
err := db.AutoMigrate(
&entity.Doctor{},
&entity.ECG{},
&entity.Patient{},
&entity.SleepData{},
&entity.SleepStage{},
&entity.SleepQuality{},
)
if err != nil {
return err
}
return nil
}
func InsertTestData(db *gorm.DB) {
// Contoh data Doctor
doctorData := entity.Doctor{
FirstName: "John",
LastName: "Doe",
Gender: "Male",
Email: "john.doe@example.com",
PhoneNumber: "123456789",
BirthRate: time.Date(1980, 1, 1, 0, 0, 0, 0, time.UTC),
RegistrationDate: time.Now(),
Address: "123 Main St, City",
}
// Contoh data Patient
patientData := entity.Patient{
FirstName: "Alice",
LastName: "Johnson",
Gender: "Female",
Email: "alice.johnson@example.com",
PhoneNumber: "987654321",
BirthDate: time.Date(1990, 5, 15, 0, 0, 0, 0, time.UTC),
RegistrationDate: time.Now(),
Address: "456 Oak St, Town",
SensorToken: "sensortoken123",
StartSleepTime: time.Now(),
EndSleepTime: time.Now().Add(8 * time.Hour), // Contoh: tidur selama 8 jam
}
// Sisipkan data ke database
db.Create(&doctorData)
db.Create(&patientData)
fmt.Println("Data inserted successfully.")
}

View File

@@ -0,0 +1,76 @@
package entity
import (
"time"
)
// Doctor represents the Doctor table
type Doctor struct {
ID uint `gorm:"primaryKey" json:"id"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Gender string `json:"gender,omitempty"`
Email string `json:"email,omitempty"`
PhoneNumber string `json:"phone_number,omitempty"`
BirthRate time.Time `json:"birth_rate,omitempty"`
RegistrationDate time.Time `json:"registration_date,omitempty"`
Address string `json:"address,omitempty"`
}
// Patient represents the Patient table
type Patient struct {
ID uint `gorm:"primaryKey" json:"id"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Gender string `json:"gender,omitempty"`
Email string `json:"email,omitempty"`
PhoneNumber string `json:"phone_number,omitempty"`
BirthDate time.Time `json:"birth_date,omitempty"`
RegistrationDate time.Time `json:"registration_date,omitempty"`
Address string `json:"address,omitempty"`
SensorToken string `json:"sensor_token,omitempty"`
StartSleepTime time.Time `json:"start_sleep_time,omitempty"`
EndSleepTime time.Time `json:"end_sleep_time,omitempty"`
}
// SleepData represents the Sleep Data table
type SleepData struct {
ID uint `gorm:"primaryKey" json:"id"`
PatientID uint `json:"patient_id,omitempty"`
FirstECGID uint `json:"first_ecg_id,omitempty"`
FirstSleepStageID uint `json:"first_sleep_stage_id,omitempty"`
SleepQualityID uint `json:"sleep_quality_id,omitempty"`
FirstInputTime time.Time `json:"first_input_time,omitempty"`
LastInputTime time.Time `json:"last_input_time,omitempty"`
}
// ECG represents the ECG table
type ECG struct {
ID uint `gorm:"primaryKey" json:"id"`
ReferenceID uint `json:"reference_id,omitempty"`
Value float64 `json:"value,omitempty"`
InputTime time.Time `json:"input_time,omitempty"`
}
// SleepStage represents the Sleep Stage table
type SleepStage struct {
ID uint `gorm:"primaryKey" json:"id"`
ReferenceID uint `json:"reference_id,omitempty"`
Value string `json:"value,omitempty"`
Method string `json:"method,omitempty"`
}
// SleepQuality represents the Sleep Quality table
type SleepQuality struct {
ID uint `gorm:"primaryKey" json:"id"`
Value string `json:"value,omitempty"`
InBedDuration time.Time `json:"in_bed_duration,omitempty"`
BeginToSleepTime time.Time `json:"begin_to_sleep_time,omitempty"`
AwakeFromSleepTime time.Time `json:"awake_from_sleep_time,omitempty"`
SleepEfficiency float64 `json:"sleep_efficiency,omitempty"`
LightSleepDuration time.Time `json:"light_sleep_duration,omitempty"`
DeepSleepDuration time.Time `json:"deep_sleep_duration,omitempty"`
REMDuration time.Time `json:"rem_duration,omitempty"`
AwakeDuration time.Time `json:"awake_duration,omitempty"`
InputTime time.Time `json:"input_time,omitempty"`
}

View File

@@ -0,0 +1,353 @@
package handler
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/mitchellh/mapstructure"
"github.com/stanleydv12/gateway-classification/src/entity"
"gorm.io/gorm"
)
var saveTimer *time.Timer
// HandleEvent handles the given event and data.
//
// Parameters:
// - event: a string representing the event to be handled.
// - data: an interface{} containing the data related to the event.
//
// Returns: None.
func HandleEvent(event string, data interface{}) {
switch event {
case "save-data":
sensorData, ok := convertToSensorData(data)
if !ok {
fmt.Println("HandleEvent: Failed to convert data to SensorData")
return
}
SaveData(sensorData)
default:
fmt.Println("HandleEvent: unknown event")
}
}
// SaveData saves the provided data to the database if it meets certain conditions.
//
// The function takes in a data interface{} parameter, which should be of type entity.ECG.
// It first checks if the data is of the correct type using type assertion.
// If the data is not of type entity.ECG, the function prints an error message and returns.
//
// The function then parses the input timestamp of the data and checks if it is valid.
// If there is an error in parsing the timestamp, the function prints an error message and returns.
//
// The function calculates the time difference between the current time and the input timestamp.
// If the time difference is greater than 30 seconds, the function prints a message and returns.
//
// If the data is the first ECG for a patient, the function sets the ReferenceID to 0.
// Otherwise, it retrieves the ReferenceID for the patient using the GetReferenceIDForPatient function.
//
// The function creates a new instance of entity.ECG with the updated ReferenceID and other attributes.
//
// The function then attempts to save the newSensorData to the database using the DB.Create function.
// If there is an error in saving the data, the function prints an error message and returns.
//
// Finally, if the data is successfully saved to the database, the function prints a success message.
func SaveData(data interface{}) {
fmt.Println(data)
sensorData, ok := data.(entity.ECG)
if !ok {
fmt.Println("SaveData: Invalid data format")
return
}
// Note: Inject input time with current time
sensorData.InputTime = time.Now()
currentTime := time.Now()
diff := currentTime.Sub(sensorData.InputTime)
if diff.Seconds() > 30 {
fmt.Println("SaveData: Data is older than 30 seconds, skipping save to DB")
return
}
if isFirstEcg := CheckIfFirstECG(sensorData); isFirstEcg {
sensorData.ReferenceID = 0
newSensorData := entity.ECG{
ReferenceID: sensorData.ReferenceID,
Value: sensorData.Value,
InputTime: sensorData.InputTime,
}
err := DB.Create(&newSensorData).Error
if err != nil {
fmt.Println("SaveData: Failed to save data to DB:", err)
return
}
firstSensorData := entity.ECG{}
DB.Select("id").First(&firstSensorData)
sleepData := entity.SleepData{
ID: 1,
PatientID: 1,
FirstECGID: firstSensorData.ID,
FirstInputTime: firstSensorData.InputTime,
}
err = DB.Save(&sleepData).Error
if err != nil {
fmt.Println("SaveData: Failed to save sleep data to DB:", err)
return
}
} else {
sensorData.ReferenceID = GetReferenceIDForPatient(sensorData)
newSensorData := entity.ECG{
ReferenceID: sensorData.ReferenceID,
Value: sensorData.Value,
InputTime: sensorData.InputTime,
}
err := DB.Create(&newSensorData).Error
if err != nil {
fmt.Println("SaveData: Failed to save data to DB:", err)
return
}
sleepData := entity.SleepData{
ID: 1,
PatientID: 1,
LastInputTime: sensorData.InputTime,
}
err = DB.Save(&sleepData).Error
if err != nil {
fmt.Println("SaveData: Failed to save sleep data to DB:", err)
return
}
}
// Reset the timer to 30 seconds
if saveTimer == nil {
saveTimer = time.NewTimer(30 * time.Second)
} else {
saveTimer.Reset(30 * time.Second)
}
fmt.Println("SaveData: Data saved to DB")
}
// convertToSensorData converts the given data to a SensorData object.
//
// data: The data to be converted.
// Returns: The converted SensorData object and a boolean indicating if the conversion was successful.
func convertToSensorData(data interface{}) (entity.ECG, bool) {
var sensorData entity.ECG
err := mapstructure.Decode(data, &sensorData)
if err != nil {
return entity.ECG{}, false
}
return sensorData, true
}
var DB *gorm.DB
// SetDBInstance sets the global variable DB to the given *gorm.DB instance.
//
// db: the *gorm.DB instance to be set.
func SetDBInstance(db *gorm.DB) {
DB = db
}
// CheckIfFirstECG checks if the given ECG sensor data is the first data recorded for a given date.
//
// It takes an ECG object as a parameter and returns a boolean value indicating whether the data is the first for that date.
func CheckIfFirstECG(sensorData entity.ECG) bool {
var count int64
var lastData entity.ECG
// Check if data is empty
DB.Model(&entity.ECG{}).Count(&count)
if count == 0 {
return true
}
// Check if the last data for reference_id is not empty
DB.Order("input_time DESC").Where("reference_id = ?", sensorData.ReferenceID).First(&lastData)
if lastData.ID != 0 {
// Check if input_time is more than 30 seconds from the current time
currentTime := time.Now()
timeDiff := currentTime.Sub(lastData.InputTime)
if timeDiff.Seconds() > 30 {
return true
}
}
return false
}
// GetReferenceIDForPatient retrieves the reference ID for a patient based on the given ECG sensor data.
//
// It takes the following parameter:
// - sensorData: an instance of the entity.ECG struct representing the ECG sensor data.
//
// It returns a uint representing the reference ID.
func GetReferenceIDForPatient(sensorData entity.ECG) uint {
var referenceID uint
DB.Model(&entity.ECG{}).Where("DATE(input_time) = ?", sensorData.InputTime.Format("2006-01-02")).Select("id").Order("id ASC").First(&referenceID)
return referenceID
}
// StartTimer starts the timer for saving data to DB and calling classifyData
func StartTimer() {
saveTimer = time.NewTimer(30 * time.Second)
go func() {
for {
select {
case <-saveTimer.C:
classifyData()
saveTimer.Reset(30 * time.Second)
}
}
}()
}
type PredictionResponse struct {
Prediction string `json:"prediction"`
}
func classifyData() {
var lastECG entity.ECG
if err := DB.Order("input_time desc").First(&lastECG).Error; err != nil {
if err == gorm.ErrRecordNotFound {
fmt.Println("No ecg data found in the database")
} else {
log.Fatalf("Failed to get last ecg data: %v", err)
}
return
}
sleepData := entity.SleepData{
ID: 1,
LastInputTime: lastECG.InputTime,
}
if err := DB.Save(&sleepData).Error; err != nil {
log.Fatalf("Failed to save sleep data: %v", err)
return
}
// Mengambil data ECG berdasarkan ID
var ecgByID []entity.ECG
if err := DB.Where("id = ?", lastECG.ReferenceID).Find(&ecgByID).Error; err != nil {
log.Fatalf("Failed to get ECG data by ID: %v", err)
return
}
// Mengambil semua data ECG berdasarkan reference ID
var ecgByReferenceID []entity.ECG
if err := DB.Where("reference_id = ?", lastECG.ReferenceID).Find(&ecgByReferenceID).Error; err != nil {
log.Fatalf("Failed to get ECG data by reference ID: %v", err)
return
}
// Combine data retrieved by ID and reference ID
allECG := append(ecgByID, ecgByReferenceID...)
// Process data in batches of 10
for i := 0; i < len(allECG); i += 10 {
end := i + 10
if end > len(allECG) {
end = len(allECG)
}
batch := allECG[i:end]
// Call the function to handle the API call for this batch
prediction, err := predict(batch)
if err != nil {
log.Fatalf("Failed to make prediction: %v", err)
return
}
if i == 0 {
sleepStage := entity.SleepStage{
ID: 1,
ReferenceID: 0,
Value: prediction.Prediction,
}
err = DB.Save(&sleepStage).Error
if err != nil {
log.Fatalf("Failed to save sleep stage: %v", err)
return
}
var sleepData entity.SleepData
err = DB.Find(&sleepData, "id = ?", 1).First(&sleepData).Error
if err != nil {
log.Fatalf("Failed to get sleep data: %v", err)
return
}
sleepData.FirstSleepStageID = sleepStage.ID
} else {
sleepStage := entity.SleepStage{
ReferenceID: 1,
Value: prediction.Prediction,
}
err = DB.Save(&sleepStage).Error
if err != nil {
log.Fatalf("Failed to save sleep stage: %v", err)
return
}
}
}
}
func predict(data []entity.ECG) (PredictionResponse, error) {
extractedValues := make([]float64, len(data))
for i, ecg := range data {
extractedValues[i] = ecg.Value
}
// Convert the extracted data slice to JSON
jsonData, err := json.Marshal(data)
if err != nil {
return PredictionResponse{}, err
}
predictURL := os.Getenv("PREDICT_URL")
if predictURL == "" {
return PredictionResponse{}, fmt.Errorf("PREDICT_URL environment variable is not set")
}
resp, err := http.Post(predictURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return PredictionResponse{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return PredictionResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
var predictionResponse PredictionResponse
if err := json.NewDecoder(resp.Body).Decode(&predictionResponse); err != nil {
return PredictionResponse{}, err
}
return predictionResponse, nil
}

View File

@@ -0,0 +1,96 @@
package mqtt
import (
"encoding/json"
"fmt"
"log"
"os"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/joho/godotenv"
"github.com/stanleydv12/gateway-classification/src/handler"
)
type Message struct {
Event string `json:"event"`
Data interface{} `json:"data"`
}
var Client mqtt.Client
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("Message Received from [%s] : %s\n", msg.Topic(), msg.Payload())
var receivedMessage Message
err := json.Unmarshal(msg.Payload(), &receivedMessage)
if err != nil {
fmt.Println("Error decoding message:", err)
return
}
handler.HandleEvent(receivedMessage.Event, receivedMessage.Data)
}
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
fmt.Println("Connected")
Sub(client)
}
var onLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
fmt.Printf("Connection lost : %v", err)
}
// SetupMqtt sets up the MQTT connection.
//
// It loads the environment variables from the .env file.
// It creates a new MQTT client options object and configures it with the
// MQTT broker host, port, and client ID.
// It sets the default publish handler, on connect handler, and connection lost
// handler for the MQTT client options.
// It creates a new MQTT client with the configured options.
// It connects the MQTT client to the broker.
// If there is an error during the connection, it panics.
func SetupMqtt() {
errEnv := godotenv.Load(".env")
if errEnv != nil {
log.Fatal("Error loading .env")
}
broker := os.Getenv("MQTT_BROKER_HOST")
port := os.Getenv("MQTT_BROKER_PORT")
clientID := os.Getenv("MQTT_CLIENT_ID")
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("ws://%s:%s", broker, port))
opts.SetClientID(clientID)
opts.SetDefaultPublishHandler(messagePubHandler)
opts.SetOnConnectHandler(connectHandler)
opts.SetConnectionLostHandler(onLostHandler)
Client = mqtt.NewClient(opts)
if token := Client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
}
// Sub is a function that subscribes to an MQTT topic and prints a message.
//
// It takes a client of type mqtt.Client as a parameter.
// It does not return any value.
func Sub(client mqtt.Client) {
topic := os.Getenv("MQTT_TOPIC")
token := client.Subscribe(topic, 1, nil)
token.Wait()
fmt.Printf("Subscribed to topic %s\n", topic)
}
// Pub publishes the given message to the MQTT topic.
//
// It takes a single parameter, msg, which is of type interface{}.
// The function does not return any value.
func Pub(msg interface{}) {
topic := os.Getenv("MQTT_TOPIC")
token := Client.Publish(topic, 1, false, msg)
token.Wait()
}

17
quantification/go.mod Normal file
View File

@@ -0,0 +1,17 @@
module github.com/dije07/sqqs
go 1.21.3
require (
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/go-redis/redis/v8 v8.11.5
github.com/lib/pq v1.10.9
)
require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gorilla/websocket v1.5.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sync v0.1.0 // indirect
)

32
quantification/go.sum Normal file
View File

@@ -0,0 +1,32 @@
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@@ -0,0 +1,390 @@
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"math"
"os"
"time"
"github.com/go-redis/redis/v8"
_ "github.com/lib/pq"
MQTT "github.com/eclipse/paho.mqtt.golang"
)
// Define your MQTT broker address and port
const (
brokerAddress = "ws://localhost:9001" // Change this to your MQTT broker address
host = "localhost"
port = 5432
user = "postgres"
password = "postgres"
dbname = "sleep_monitoring"
)
var DB *sql.DB
type Message struct {
Event string `json:"event"`
Data interface{} `json:"data"`
}
type SleepQuality struct {
ID uint `gorm:"primaryKey" json:"id"`
Value string `json:"value,omitempty"`
DeepSleepDuration float64 `json:"deep_sleep_duration,omitempty"`
AwakeDuration float64 `json:"awake_duration,omitempty"`
TotalSleepDuration float64 `json:"total_sleep_duration,omitempty"`
InputTime time.Time `json:"input_time,omitempty"`
UserId uint `json:"user_id,omitempty"`
}
// FuzzySet represents a fuzzy set with a membership function.
type FuzzySet struct {
Name string
Terms map[string]func(float64) float64
}
// Fuzzify function calculates the membership values for each term in the fuzzy set.
func (fs *FuzzySet) Fuzzify(value float64) string {
max := 0.0
result := ""
for term, mf := range fs.Terms {
membership := mf(value)
if membership > max {
max = membership
result = term
}
}
return result
}
// ApplyRule function evaluates the conditions and returns the corresponding action.
func ApplyRule(conditions map[string]float64) (string, bool) {
// Apply all rules
switch {
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 1:
return "LEVEL 9", true
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 8", true
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 0:
return "LEVEL 7", true
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 1:
return "LEVEL 7", true
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 6", true
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 0:
return "LEVEL 5", true
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 1:
return "LEVEL 5", true
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 4", true
case conditions["awakeDuration"] == 1 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 0:
return "LEVEL 3", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 1:
return "LEVEL 8", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 7", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 0:
return "LEVEL 6", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 1:
return "LEVEL 6", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 5", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 0:
return "LEVEL 4", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 1:
return "LEVEL 4", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 3", true
case conditions["awakeDuration"] == 0.5 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 0:
return "LEVEL 2", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 1:
return "LEVEL 7", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 6", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 1 && conditions["totalSleepTime"] == 0:
return "LEVEL 5", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 1:
return "LEVEL 5", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 4", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 0.5 && conditions["totalSleepTime"] == 0:
return "LEVEL 3", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 1:
return "LEVEL 3", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 0.5:
return "LEVEL 2", true
case conditions["awakeDuration"] == 0 && conditions["deepSleepTime"] == 0 && conditions["totalSleepTime"] == 0:
return "LEVEL 1", true
}
// No rule matched
return "", false
}
var messagePubHandler MQTT.MessageHandler = func(client MQTT.Client, msg MQTT.Message) {
fmt.Printf("Message Received from [%s] : %s\n", msg.Topic(), msg.Payload())
if string(msg.Payload()) == "sleepStageReady" {
quantifyData()
}
}
func setUpDB() *sql.DB {
// Construct the connection string
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
os.Getenv("DB_HOST"), os.Getenv("DB_PORT"),
os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"),
os.Getenv("DB_NAME"))
// Open a connection to the database
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal("Error connecting to the database: ", err)
}
// Attempt to ping the database to verify connection
err = db.Ping()
if err != nil {
log.Fatal("Error pinging the database: ", err)
}
fmt.Println("Successfully connected to the database!")
return db
}
var red *redis.Client
func quantifyData() {
var awake float64 = 0
var deepSleep float64 = 0
var totalSleep float64 = 0
db := setUpDB()
DB = db
value, err := red.Get(red.Context(), "sleep-stages").Result()
if value == "" || err != nil {
// Example query
start := time.Now().UnixMilli()
rows, err := DB.Query("SELECT * FROM sleep_stages")
end := time.Now().UnixMilli()
fmt.Println("Start : ", start)
fmt.Println("End : ", end)
fmt.Println("Duration : ", end-start, "ms")
if err != nil {
log.Fatal("Error querying the database: ", err)
}
defer rows.Close()
// Iterate over the rows
for rows.Next() {
var id int
var reference_id string
var value string
var method any
var timestamp time.Time
err := rows.Scan(&id, &reference_id, &value, &method, &timestamp)
if err != nil {
log.Println("Error scanning row: ", err)
}
if value == "N1" || value == "N2" || value == "REM" {
totalSleep++
}
if value == "N3" {
totalSleep++
deepSleep++
}
if value == "AWAKE" {
awake++
}
}
if err := rows.Err(); err != nil {
log.Println("Error iterating over rows: ", err)
}
} else {
type SleepStage struct {
ID uint `json:"id"`
ReferenceID uint `json:"reference_id"`
Value string `json:"value"`
Method string `json:"method"`
Timestamp time.Time `json:"timestamp"`
}
type SleepStages struct {
Stages []SleepStage `json:"sleep-stages"`
}
// Unmarshal the JSON into a SleepStages struct
var stages SleepStages
err := json.Unmarshal([]byte(value), &stages)
if err != nil {
log.Println(err)
}
// Loop through the stages and print values
for _, stage := range stages.Stages {
if stage.Value == "N1" || stage.Value == "N2" || stage.Value == "REM" {
totalSleep++
}
if stage.Value == "N3" {
totalSleep++
deepSleep++
}
if stage.Value == "AWAKE" {
awake++
}
}
}
// Define fuzzy sets and input values
awakeDurationSet := FuzzySet{
Name: "awakeDuration",
Terms: map[string]func(float64) float64{
"low": func(x float64) float64 { return math.Max(0, 1.0-0.5*x) },
"medium": func(x float64) float64 { return math.Max(0, 1-2*math.Abs(x-0.5)) },
"high": func(x float64) float64 { return math.Max(0, 2*x-1) },
},
}
deepSleepDurationSet := FuzzySet{
Name: "awakeDuration",
Terms: map[string]func(float64) float64{
"low": func(x float64) float64 { return math.Max(0, 1.0-0.5*x) },
"medium": func(x float64) float64 { return math.Max(0, 1-2*math.Abs(x-0.5)) },
"high": func(x float64) float64 { return math.Max(0, 2*x-1) },
},
}
totalSleepDurationSet := FuzzySet{
Name: "awakeDuration",
Terms: map[string]func(float64) float64{
"low": func(x float64) float64 { return math.Max(0, 1.0-0.5*x) },
"medium": func(x float64) float64 { return math.Max(0, 1-2*math.Abs(x-0.5)) },
"high": func(x float64) float64 { return math.Max(0, 2*x-1) },
},
}
inputValues := map[string]float64{
"awakeDuration": awake / 60,
"deepSleepTime": deepSleep / 60,
"totalSleepTime": totalSleep / 60,
}
// Fuzzify the sleep data
awakeTerm := awakeDurationSet.Fuzzify(inputValues["awakeDuration"])
deepSleepTerm := deepSleepDurationSet.Fuzzify(inputValues["deepSleepTime"])
totalSleepTerm := totalSleepDurationSet.Fuzzify(inputValues["totalSleepTime"])
// Fuzzify the other sleep data...
// Print the intermediate results
fmt.Println("Fuzzy Conditions:")
fmt.Printf("Awake Duration: %s (%f)\n", awakeTerm, inputValues["awakeDuration"])
fmt.Printf("Deep Sleep Duration: %s (%f)\n", deepSleepTerm, inputValues["deepSleepTime"])
fmt.Printf("Total Sleep Duration: %s (%f)\n", totalSleepTerm, inputValues["totalSleepTime"])
// Print the other fuzzy conditions...
// Map the linguistic terms to their respective numerical values
linguisticValues := map[string]float64{
"high": 1.0,
"medium": 0.5,
"low": 0.0,
}
// Create a map for all conditions
allConditions := map[string]float64{
"awakeDuration": linguisticValues[awakeTerm],
"deepSleepTime": linguisticValues[deepSleepTerm],
"totalSleepTime": linguisticValues[totalSleepTerm],
}
action, ruleMatched := ApplyRule(allConditions)
// Print the final results
if ruleMatched {
fmt.Println("\nRule Matched! Action:", action)
} else {
fmt.Println("\nNo Rule Matched.")
}
sleepQuality := SleepQuality{
Value: action,
DeepSleepDuration: inputValues["deepSleepTime"],
TotalSleepDuration: inputValues["totalSleepTime"],
AwakeDuration: inputValues["awakeDuration"],
InputTime: time.Now(),
UserId: 1,
}
sleepQualityJSON, err := json.Marshal(sleepQuality)
if err != nil {
fmt.Println("Error:", err)
return
}
err = red.Set(red.Context(), "sleep_quality_1", sleepQualityJSON, 0).Err()
if err != nil {
log.Fatalf("Failed to save to redis: %v", err)
} else {
fmt.Println("Saved to redis")
}
// Prepare the SQL statement for inserting data.
stmt, err := db.Prepare("INSERT INTO sleep_qualities (value, deep_sleep_duration, awake_duration, total_sleep_duration, input_time, user_id) VALUES ($1, $2, $3, $4, $5, $6)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
// Example data to be inserted.
value1 := action
value2 := inputValues["deepSleepTime"]
value3 := inputValues["awakeDuration"]
value4 := inputValues["totalSleepTime"]
value5 := time.Now()
value6 := 1
// Execute the SQL statement with the provided values.
_, err = stmt.Exec(value1, value2, value3, value4, value5, value6)
if err != nil {
log.Println(err)
}
}
func main() {
red = redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_ADDR"), // Redis server address
Password: "", // no password set
DB: 0, // use default DB
})
// Set MQTT client options
opts := MQTT.NewClientOptions().AddBroker("ws://" + os.Getenv("MQTT_BROKER"))
opts.SetClientID("MQTT_simulator")
opts.SetDefaultPublishHandler(messagePubHandler)
topic := "sleep_monitoring"
// Create and start a client using the above options
client := MQTT.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
fmt.Println("Error connecting to MQTT broker:", token.Error())
os.Exit(1)
}
// Subscribe to MQTT topics
if token := client.Subscribe(topic, 0, messagePubHandler); token.Wait() && token.Error() != nil {
fmt.Println("Error subscribing to MQTT topic:", token.Error())
os.Exit(1)
}
fmt.Printf("Subscribed to topic: %s\n", topic)
// Keep the program running indefinitely
select {}
}