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,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()
}