Add files via upload

This commit is contained in:
dije07
2024-04-01 16:27:16 +07:00
committed by GitHub
commit f254371284
17 changed files with 1768 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,44 @@
# 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 Instalasi Kubernetes Cluster menggunakan Terraform
Berikut adalah langkah-langkah untuk menggunakan Terraform untuk menerapkan kluster Kubernetes di lingkungan Anda.
## Persyaratan Sebelum Instalasi:
1. Pastikan Anda memiliki akun layanan cloud (seperti AWS, GCP, atau Azure) dan telah mengkonfigurasi kredensial akses yang sesuai.
2. Instalasi Terraform di komputer lokal Anda. Anda dapat mengunduh Terraform dari situs web resmi mereka. Direkomendasikan menggunakan Google Cloud Console dimana sudah terdapat instalasi terraform nya.
## Langkah-langkah:
```bash
$ terraform init
$ terraform plan
$ terraform apply
```
# 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()
}

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
)

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 {}
}