From b822f2becc00eeca49ca48faa5af98b18c845fff Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Fri, 26 Jan 2024 13:02:46 +0300
Subject: [PATCH 1/9] unstable reconnect

---
 cmd/main.go                | 18 +++++++----
 pkg/midi/device_health.go  | 21 -------------
 pkg/midi/device_manager.go | 31 ++-----------------
 pkg/midi/in.go             | 13 +++++---
 pkg/midi/midi_device.go    | 61 +++++++++++++++++++++++++++++++-------
 pkg/midi/out.go            |  5 ++++
 6 files changed, 79 insertions(+), 70 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go
index 9ebf275..b5a5953 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -2,17 +2,18 @@ package main
 
 import (
 	"fmt"
+	"log"
+	"midi_manipulator/pkg/backlight"
+	"midi_manipulator/pkg/config"
+	midiHermophrodite "midi_manipulator/pkg/midi"
+	"midi_manipulator/pkg/model"
+
 	"git.miem.hse.ru/hubman/hubman-lib"
 	"git.miem.hse.ru/hubman/hubman-lib/core"
 	"git.miem.hse.ru/hubman/hubman-lib/executor"
 	"gitlab.com/gomidi/midi/v2"
 	_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
 	"go.uber.org/zap"
-	"log"
-	"midi_manipulator/pkg/backlight"
-	"midi_manipulator/pkg/config"
-	midiHermophrodite "midi_manipulator/pkg/midi"
-	"midi_manipulator/pkg/model"
 )
 
 func main() {
@@ -51,7 +52,7 @@ func setupApp(systemConfig *core.SystemConfig, userConfig *config.UserConfig) {
 	}
 
 	deviceManager.UpdateDevices(userConfig.MidiDevices)
-	go midiHermophrodite.CheckDevicesHealth(deviceManager)
+	// go midiHermophrodite.CheckDevicesHealth(deviceManager)
 
 	signals := deviceManager.GetSignals()
 	app := core.NewContainer(agentConf.System.Logging)
@@ -123,6 +124,11 @@ func setupApp(systemConfig *core.SystemConfig, userConfig *config.UserConfig) {
 			hubman.WithOnConfigRefresh(func(configuration core.AgentConfiguration) {
 				update, _ := configuration.User.(*config.UserConfig)
 				deviceManager.UpdateDevices(update.MidiDevices)
+				// go func () {
+				// 	for err != nil {
+				// 		unreachableDevs, err = deviceManager.UpdateDevices(unreachableDevs)
+				// 	}
+				// }
 			}),
 		),
 	)
diff --git a/pkg/midi/device_health.go b/pkg/midi/device_health.go
index 0b87138..d4d0eef 100644
--- a/pkg/midi/device_health.go
+++ b/pkg/midi/device_health.go
@@ -1,34 +1,13 @@
 package midi
 
 import (
-	"gitlab.com/gomidi/midi/v2"
 	"gitlab.com/gomidi/midi/v2/drivers"
-	"go.uber.org/zap"
-	"os"
 	"strings"
 	"time"
 )
 
 var healthCheckDelay = 400 * time.Millisecond
 
-func CheckDevicesHealth(manager *DeviceManager) {
-	for {
-		manager.dMutex.Lock()
-		for _, deviceName := range manager.deviceNames {
-			if !HasDeviceWithName(deviceName, midi.GetInPorts()) {
-				manager.logger.Error("InPort is unreachable for device", zap.String("alias", deviceName))
-				os.Exit(1)
-			}
-			if !HasDeviceWithName(deviceName, midi.GetOutPorts()) {
-				manager.logger.Error("OutPort is unreachable for device", zap.String("alias", deviceName))
-				os.Exit(1)
-			}
-		}
-		manager.dMutex.Unlock()
-		time.Sleep(healthCheckDelay)
-	}
-}
-
 func HasDeviceWithName[T drivers.Port](deviceName string, deviceList []T) bool {
 	for _, portName := range deviceList {
 		validPortName := GetValidPortName(portName.String())
diff --git a/pkg/midi/device_manager.go b/pkg/midi/device_manager.go
index 928e353..86a3bb2 100644
--- a/pkg/midi/device_manager.go
+++ b/pkg/midi/device_manager.go
@@ -21,9 +21,6 @@ type DeviceManager struct {
 
 	activeNamespace string
 	nmMutex         sync.RWMutex
-
-	deviceNames []string
-	dMutex      sync.Mutex
 }
 
 func (dm *DeviceManager) SetBacklightConfig(cfg *backlight.DecodedDeviceBacklightConfig) {
@@ -43,28 +40,12 @@ func (dm *DeviceManager) addDevice(device *MidiDevice) {
 	dm.devices[device.GetAlias()] = device
 }
 
-func (dm *DeviceManager) addDeviceName(deviceName string) {
-	dm.dMutex.Lock()
-	defer dm.dMutex.Unlock()
-	dm.deviceNames = append(dm.deviceNames, deviceName)
-}
-
 func (dm *DeviceManager) removeDevice(alias string) {
 	dm.mutex.Lock()
 	defer dm.mutex.Unlock()
 	delete(dm.devices, alias)
 }
 
-func (dm *DeviceManager) removeDeviceName(alias string) {
-	dm.dMutex.Lock()
-	defer dm.dMutex.Unlock()
-	for i, deviceName := range dm.deviceNames {
-		if deviceName == alias {
-			dm.deviceNames = append(dm.deviceNames[:i], dm.deviceNames[i+1:]...)
-		}
-	}
-}
-
 func (dm *DeviceManager) Close() {
 	for _, device := range dm.devices {
 		err := dm.RemoveDevice(device.GetAlias())
@@ -105,8 +86,7 @@ func (dm *DeviceManager) AddDevice(device *MidiDevice) error {
 	}
 
 	dm.addDevice(device)
-	dm.addDeviceName(device.GetAlias())
-	err := device.RunDevice(dm.signals, dm.backlightConfig)
+	err := device.RunDevice(dm.backlightConfig)
 
 	if err != nil {
 		return err
@@ -130,7 +110,6 @@ func (dm *DeviceManager) RemoveDevice(alias string) error {
 	}
 
 	dm.removeDevice(alias)
-	dm.removeDeviceName(alias)
 
 	return nil
 }
@@ -146,13 +125,9 @@ func (dm *DeviceManager) UpdateDevices(midiConfig []config.DeviceConfig) {
 	for _, deviceConfig := range midiConfigMap {
 		device, found := dm.getDevice(deviceConfig.DeviceName)
 		if !found {
-			newDevice, err := NewDevice(deviceConfig)
-
-			if err != nil {
-				panic(err)
-			}
+			newDevice, _ := NewDevice(deviceConfig, dm.signals)
 
-			err = dm.AddDevice(newDevice)
+			err := dm.AddDevice(newDevice)
 
 			if err != nil {
 				panic(err)
diff --git a/pkg/midi/in.go b/pkg/midi/in.go
index d05a148..a9d3970 100644
--- a/pkg/midi/in.go
+++ b/pkg/midi/in.go
@@ -8,10 +8,10 @@ import (
 	"time"
 )
 
-func (md *MidiDevice) sendSignal(signals chan<- core.Signal, signal core.Signal) {
+func (md *MidiDevice) sendSignal(signal core.Signal) {
 	if signal != nil {
 		log.Println(signal.Code(), signal)
-		signals <- signal
+		md.signals <- signal
 	}
 }
 
@@ -110,7 +110,12 @@ func (md *MidiDevice) messageToSignal() []core.Signal {
 	return signalSequence
 }
 
-func (md *MidiDevice) listen(signals chan<- core.Signal) {
+func (md *MidiDevice) listen() {
+	md.mutex.Lock()
+	if !md.connected {
+		md.mutex.Unlock()
+		return
+	}
 	stop, err := midi.ListenTo(*md.ports.in, md.getMidiMessage, midi.UseSysEx())
 
 	if err != nil {
@@ -126,7 +131,7 @@ func (md *MidiDevice) listen(signals chan<- core.Signal) {
 			return
 		default:
 			for _, signal := range signalSequence {
-				md.sendSignal(signals, signal)
+				md.sendSignal(signal)
 			}
 		}
 	}
diff --git a/pkg/midi/midi_device.go b/pkg/midi/midi_device.go
index c1acc9d..7824c62 100644
--- a/pkg/midi/midi_device.go
+++ b/pkg/midi/midi_device.go
@@ -22,7 +22,9 @@ type MidiDevice struct {
 	mutex       sync.Mutex
 	stop        chan bool
 	namespace   string
+	connected 	bool
 	controls    map[byte]*Control
+	signals		chan<- core.Signal
 }
 
 type MidiPorts struct {
@@ -55,19 +57,53 @@ func (md *MidiDevice) StopDevice() error {
 	return nil
 }
 
-func (md *MidiDevice) RunDevice(signals chan<- core.Signal, backlightConfig *backlight.DecodedDeviceBacklightConfig) error {
+func (md *MidiDevice) RunDevice(backlightConfig *backlight.DecodedDeviceBacklightConfig) error {
 	go md.startupIllumination(backlightConfig)
-	go md.listen(signals)
+	go md.listen()
+	go md.reconnect(backlightConfig)
 	return nil
 }
 
+func (md *MidiDevice) reconnect(backlightConfig *backlight.DecodedDeviceBacklightConfig) error {
+	log.Println("RECONNECT")
+	for {
+		md.mutex.Lock()
+		
+		if md.connected && (!HasDeviceWithName(md.name, midi.GetInPorts()) || !HasDeviceWithName(md.name, midi.GetOutPorts()))  {
+			// manager.logger.Error("In/Out ports are unreachable for device", zap.String("alias", deviceName))
+			md.connected = false
+			md.stop <- true
+		}
+
+		if !md.connected {
+			// manager.logger.Error("In/Out ports are unreachable for device", zap.String("alias", deviceName))
+			err := md.connectDevice()
+
+			if err == nil {
+				time.Sleep(4000 * time.Millisecond)
+				md.startupIllumination(backlightConfig)
+				go md.listen()
+				log.Println(md.name, "was reconnected")
+			}
+		}
+
+		md.mutex.Unlock()
+		time.Sleep(2000 * time.Millisecond)
+	}
+}
+
+
 func (md *MidiDevice) connectDevice() error {
-	var err error
+	var err error = nil
 	in_err := md.connectInPort()
 	out_err := md.connectOutPort()
 
 	if in_err != nil || out_err != nil {
 		err = fmt.Errorf("connection of device \"{%s}\" failed", md.name)
+	} 
+	if in_err == nil && out_err == nil {
+		md.connected = true
+		err = nil
 	}
 	return err
 }
@@ -106,7 +142,7 @@ func (md *MidiDevice) connectInPort() error {
 	return nil
 }
 
-func (md *MidiDevice) applyConfiguration(deviceConfig config.DeviceConfig) {
+func (md *MidiDevice) applyConfiguration(deviceConfig config.DeviceConfig, signals chan<- core.Signal) {
 	md.name = deviceConfig.DeviceName
 	md.active = deviceConfig.Active
 	md.holdDelta = time.Duration(float64(time.Millisecond) * deviceConfig.HoldDelta)
@@ -114,6 +150,8 @@ func (md *MidiDevice) applyConfiguration(deviceConfig config.DeviceConfig) {
 	md.stop = make(chan bool, 1)
 	md.namespace = deviceConfig.Namespace
 	md.controls = make(map[byte]*Control)
+	md.connected = false
+	md.signals = signals
 	md.applyControls(deviceConfig.Controls)
 }
 
@@ -132,18 +170,19 @@ func (md *MidiDevice) applyControls(controls config.Controls) {
 }
 
 func (md *MidiDevice) updateConfiguration(config config.DeviceConfig) {
+	md.mutex.Lock()
+	defer md.mutex.Unlock()
 	md.active = config.Active
 	md.holdDelta = time.Duration(float64(time.Millisecond) * config.HoldDelta)
 }
 
-func NewDevice(deviceConfig config.DeviceConfig) (*MidiDevice, error) {
+func NewDevice(deviceConfig config.DeviceConfig, signals chan<- core.Signal) (*MidiDevice, error) {
 	midiDevice := MidiDevice{}
-	midiDevice.applyConfiguration(deviceConfig)
+	midiDevice.applyConfiguration(deviceConfig, signals)
 
 	err := midiDevice.connectDevice()
-	if err != nil {
-		fmt.Println(err)
-		return nil, err
-	}
-	return &midiDevice, nil
+
+	log.Println(err)
+
+	return &midiDevice, err
 }
diff --git a/pkg/midi/out.go b/pkg/midi/out.go
index 726b258..54ae79b 100644
--- a/pkg/midi/out.go
+++ b/pkg/midi/out.go
@@ -47,6 +47,11 @@ func (md *MidiDevice) singleReversedBlink(cmd model.SingleReversedBlinkCommand,
 }
 
 func (md *MidiDevice) startupIllumination(config *backlight.DecodedDeviceBacklightConfig) {
+	md.mutex.Lock()
+	if !md.connected {
+		md.mutex.Unlock()
+		return
+	}
 	backlightTimeOffset := time.Duration(config.DeviceBacklightTimeOffset[md.name])
 	for _, keyRange := range config.DeviceKeyRangeMap[md.name] {
 		for i := keyRange[0]; i <= keyRange[1]; i++ {
-- 
GitLab


From 63d0321d095b5d0d49130a51ffc1454a6ee873c6 Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Fri, 26 Jan 2024 16:50:44 +0300
Subject: [PATCH 2/9] stable reconnect v1

---
 pkg/midi/device_manager.go | 14 +++---------
 pkg/midi/in.go             |  2 ++
 pkg/midi/midi_device.go    | 47 +++++++++++++++++---------------------
 pkg/midi/out.go            |  1 +
 4 files changed, 27 insertions(+), 37 deletions(-)

diff --git a/pkg/midi/device_manager.go b/pkg/midi/device_manager.go
index 86a3bb2..4771d5a 100644
--- a/pkg/midi/device_manager.go
+++ b/pkg/midi/device_manager.go
@@ -86,11 +86,7 @@ func (dm *DeviceManager) AddDevice(device *MidiDevice) error {
 	}
 
 	dm.addDevice(device)
-	err := device.RunDevice(dm.backlightConfig)
-
-	if err != nil {
-		return err
-	}
+	device.RunDevice(dm.backlightConfig)
 
 	return nil
 }
@@ -125,13 +121,9 @@ func (dm *DeviceManager) UpdateDevices(midiConfig []config.DeviceConfig) {
 	for _, deviceConfig := range midiConfigMap {
 		device, found := dm.getDevice(deviceConfig.DeviceName)
 		if !found {
-			newDevice, _ := NewDevice(deviceConfig, dm.signals)
+			newDevice, _ := NewDevice(deviceConfig, dm.signals, dm.logger)
 
-			err := dm.AddDevice(newDevice)
-
-			if err != nil {
-				panic(err)
-			}
+			dm.AddDevice(newDevice)
 		} else {
 			device.updateConfiguration(deviceConfig)
 		}
diff --git a/pkg/midi/in.go b/pkg/midi/in.go
index a9d3970..5964be7 100644
--- a/pkg/midi/in.go
+++ b/pkg/midi/in.go
@@ -116,6 +116,8 @@ func (md *MidiDevice) listen() {
 		md.mutex.Unlock()
 		return
 	}
+	md.mutex.Unlock()
+
 	stop, err := midi.ListenTo(*md.ports.in, md.getMidiMessage, midi.UseSysEx())
 
 	if err != nil {
diff --git a/pkg/midi/midi_device.go b/pkg/midi/midi_device.go
index 7824c62..e5b75be 100644
--- a/pkg/midi/midi_device.go
+++ b/pkg/midi/midi_device.go
@@ -5,12 +5,12 @@ import (
 	"git.miem.hse.ru/hubman/hubman-lib/core"
 	"gitlab.com/gomidi/midi/v2"
 	"gitlab.com/gomidi/midi/v2/drivers"
-	"log"
 	"midi_manipulator/pkg/backlight"
 	"midi_manipulator/pkg/config"
 	"midi_manipulator/pkg/model"
 	"sync"
 	"time"
+	"go.uber.org/zap"
 )
 
 type MidiDevice struct {
@@ -22,9 +22,10 @@ type MidiDevice struct {
 	mutex       sync.Mutex
 	stop        chan bool
 	namespace   string
-	connected 	bool
+	connected   bool
 	controls    map[byte]*Control
-	signals		chan<- core.Signal
+	signals     chan<- core.Signal
+	logger      *zap.Logger
 }
 
 type MidiPorts struct {
@@ -58,32 +59,32 @@ func (md *MidiDevice) StopDevice() error {
 }
 
 func (md *MidiDevice) RunDevice(backlightConfig *backlight.DecodedDeviceBacklightConfig) error {
-	go md.startupIllumination(backlightConfig)
+	md.startupIllumination(backlightConfig)
 	go md.listen()
 	go md.reconnect(backlightConfig)
 	return nil
 }
 
 func (md *MidiDevice) reconnect(backlightConfig *backlight.DecodedDeviceBacklightConfig) error {
-	log.Println("RECONNECT")
 	for {
 		md.mutex.Lock()
-		
-		if md.connected && (!HasDeviceWithName(md.name, midi.GetInPorts()) || !HasDeviceWithName(md.name, midi.GetOutPorts()))  {
-			// manager.logger.Error("In/Out ports are unreachable for device", zap.String("alias", deviceName))
+
+		if md.connected && (!HasDeviceWithName(md.name, midi.GetInPorts()) || !HasDeviceWithName(md.name, midi.GetOutPorts())) {
+			md.logger.Warn("Device disconnected", zap.String("alias", md.name))
 			md.connected = false
 			md.stop <- true
 		}
 
 		if !md.connected {
-			// manager.logger.Error("In/Out ports are unreachable for device", zap.String("alias", deviceName))
 			err := md.connectDevice()
 
 			if err == nil {
+				md.mutex.Unlock()
 				time.Sleep(4000 * time.Millisecond)
 				md.startupIllumination(backlightConfig)
 				go md.listen()
-				log.Println(md.name, "was reconnected")
+				md.logger.Warn("Device reconnected", zap.String("alias", md.name))
+				continue
 			}
 		}
 
@@ -92,7 +93,6 @@ func (md *MidiDevice) reconnect(backlightConfig *backlight.DecodedDeviceBackligh
 	}
 }
 
-
 func (md *MidiDevice) connectDevice() error {
 	var err error = nil
 	in_err := md.connectInPort()
@@ -100,7 +100,7 @@ func (md *MidiDevice) connectDevice() error {
 
 	if in_err != nil || out_err != nil {
 		err = fmt.Errorf("connection of device \"{%s}\" failed", md.name)
-	} 
+	}
 	if in_err == nil && out_err == nil {
 		md.connected = true
 		err = nil
@@ -111,13 +111,11 @@ func (md *MidiDevice) connectDevice() error {
 func (md *MidiDevice) connectOutPort() error {
 	port, err := midi.FindOutPort(md.name)
 	if err != nil {
-		log.Printf("Output port named {%s} was not found\n", md.name)
 		return err
 	}
 
 	port, err = midi.OutPort(port.Number())
 	if err != nil {
-		log.Printf("Output port #{%d} was not found\n", port.Number())
 		return err
 	}
 
@@ -128,13 +126,11 @@ func (md *MidiDevice) connectOutPort() error {
 func (md *MidiDevice) connectInPort() error {
 	port, err := midi.FindInPort(md.name)
 	if err != nil {
-		log.Printf("Input port named {%s} was not found", md.name)
 		return err
 	}
 
 	port, err = midi.InPort(port.Number())
 	if err != nil {
-		log.Printf("Input port #{%d} was not found\n", port.Number())
 		return err
 	}
 
@@ -142,7 +138,7 @@ func (md *MidiDevice) connectInPort() error {
 	return nil
 }
 
-func (md *MidiDevice) applyConfiguration(deviceConfig config.DeviceConfig, signals chan<- core.Signal) {
+func (md *MidiDevice) applyConfiguration(deviceConfig config.DeviceConfig, signals chan<- core.Signal, logger *zap.Logger) {
 	md.name = deviceConfig.DeviceName
 	md.active = deviceConfig.Active
 	md.holdDelta = time.Duration(float64(time.Millisecond) * deviceConfig.HoldDelta)
@@ -152,17 +148,18 @@ func (md *MidiDevice) applyConfiguration(deviceConfig config.DeviceConfig, signa
 	md.controls = make(map[byte]*Control)
 	md.connected = false
 	md.signals = signals
+	md.logger = logger
 	md.applyControls(deviceConfig.Controls)
 }
 
 func (md *MidiDevice) applyControls(controls config.Controls) {
 	for _, controlKey := range controls.Keys {
 		control := Control{
-			Key: controlKey, 
-			Rotate: controls.Rotate, 
-			ValueRange: controls.ValueRange, 
-			InitialValue: controls.InitialValue, 
-			DecrementTrigger: controls.Triggers.Decrement, 
+			Key:              controlKey,
+			Rotate:           controls.Rotate,
+			ValueRange:       controls.ValueRange,
+			InitialValue:     controls.InitialValue,
+			DecrementTrigger: controls.Triggers.Decrement,
 			IncrementTrigger: controls.Triggers.Increment,
 		}
 		md.controls[controlKey] = &control
@@ -176,13 +173,11 @@ func (md *MidiDevice) updateConfiguration(config config.DeviceConfig) {
 	md.holdDelta = time.Duration(float64(time.Millisecond) * config.HoldDelta)
 }
 
-func NewDevice(deviceConfig config.DeviceConfig, signals chan<- core.Signal) (*MidiDevice, error) {
+func NewDevice(deviceConfig config.DeviceConfig, signals chan<- core.Signal, logger *zap.Logger) (*MidiDevice, error) {
 	midiDevice := MidiDevice{}
-	midiDevice.applyConfiguration(deviceConfig, signals)
+	midiDevice.applyConfiguration(deviceConfig, signals, logger)
 
 	err := midiDevice.connectDevice()
 
-	log.Println(err)
-
 	return &midiDevice, err
 }
diff --git a/pkg/midi/out.go b/pkg/midi/out.go
index 54ae79b..d657417 100644
--- a/pkg/midi/out.go
+++ b/pkg/midi/out.go
@@ -52,6 +52,7 @@ func (md *MidiDevice) startupIllumination(config *backlight.DecodedDeviceBacklig
 		md.mutex.Unlock()
 		return
 	}
+	md.mutex.Unlock()
 	backlightTimeOffset := time.Duration(config.DeviceBacklightTimeOffset[md.name])
 	for _, keyRange := range config.DeviceKeyRangeMap[md.name] {
 		for i := keyRange[0]; i <= keyRange[1]; i++ {
-- 
GitLab


From e545bb7204e5d4296c1fd6f3bffea01864cfe40e Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Fri, 26 Jan 2024 19:09:37 +0300
Subject: [PATCH 3/9] fixed post merge conflicts, deadlocks

---
 cmd/main.go                | 8 --------
 pkg/midi/device_health.go  | 4 ----
 pkg/midi/device_manager.go | 2 +-
 pkg/midi/midi_device.go    | 3 +--
 4 files changed, 2 insertions(+), 15 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go
index f1925bf..f69199b 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -51,8 +51,6 @@ func setupApp(systemConfig *core.SystemConfig, userConfig *config.UserConfig) {
 		ParseUserConfig: func(data []byte) (core.Configuration, error) { return config.ParseConfigFromBytes(data) },
 	}
 
-	deviceManager.UpdateDevices(userConfig.MidiDevices)
-
 	signals := deviceManager.GetSignals()
 	app := core.NewContainer(agentConf.System.Logging)
 	app.RegisterPlugin(
@@ -124,17 +122,11 @@ func setupApp(systemConfig *core.SystemConfig, userConfig *config.UserConfig) {
 			hubman.WithOnConfigRefresh(func(configuration core.AgentConfiguration) {
 				update, _ := configuration.User.(*config.UserConfig)
 				deviceManager.UpdateDevices(update.MidiDevices)
-				// go func () {
-				// 	for err != nil {
-				// 		unreachableDevs, err = deviceManager.UpdateDevices(unreachableDevs)
-				// 	}
-				// }
 			}),
 		),
 	)
 
 	deviceManager.UpdateDevices(userConfig.MidiDevices)
-	go midiHermophrodite.CheckDevicesHealth(deviceManager)
 	
 	<-app.WaitShutdown()
 }
diff --git a/pkg/midi/device_health.go b/pkg/midi/device_health.go
index 849b287..54fd634 100644
--- a/pkg/midi/device_health.go
+++ b/pkg/midi/device_health.go
@@ -4,10 +4,6 @@ import (
 	"gitlab.com/gomidi/midi/v2/drivers"
 	"strings"
 	"time"
-
-	"gitlab.com/gomidi/midi/v2"
-	"gitlab.com/gomidi/midi/v2/drivers"
-	"go.uber.org/zap"
 )
 
 var healthCheckDelay = 400 * time.Millisecond
diff --git a/pkg/midi/device_manager.go b/pkg/midi/device_manager.go
index d8e2343..fcf830e 100644
--- a/pkg/midi/device_manager.go
+++ b/pkg/midi/device_manager.go
@@ -122,7 +122,7 @@ func (dm *DeviceManager) UpdateDevices(midiConfig []config.DeviceConfig) {
 
 			dm.AddDevice(newDevice)
 		} else {
-			device.updateConfiguration(deviceConfig, dm.signals)
+			device.updateConfiguration(deviceConfig)
 		}
 	}
 
diff --git a/pkg/midi/midi_device.go b/pkg/midi/midi_device.go
index dcf13aa..607717b 100644
--- a/pkg/midi/midi_device.go
+++ b/pkg/midi/midi_device.go
@@ -174,10 +174,9 @@ func (md *MidiDevice) updateConfiguration(config config.DeviceConfig) {
 	if md.namespace != config.Namespace {
 		var oldNamespace = md.namespace
 		md.namespace = config.Namespace
-		md.sendNamespaceChangedSignal(signals, oldNamespace, config.Namespace)
+		md.sendNamespaceChangedSignal(md.signals, oldNamespace, config.Namespace)
 	}
 	md.applyControls(config.Controls)
-	md.mutex.Unlock()
 }
 
 func NewDevice(deviceConfig config.DeviceConfig, signals chan<- core.Signal, logger *zap.Logger) (*MidiDevice, error) {
-- 
GitLab


From c606fc566dad52e51d29938572419fe0bc41b8ba Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Fri, 26 Jan 2024 20:54:18 +0300
Subject: [PATCH 4/9] minor fixes

---
 cmd/main.go             | 1 -
 pkg/midi/in.go          | 1 -
 pkg/midi/midi_device.go | 9 +++++----
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go
index 8dcc56e..cac85d7 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -13,7 +13,6 @@ import (
 	"git.miem.hse.ru/hubman/hubman-lib/executor"
 	"gitlab.com/gomidi/midi/v2"
 	_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
-	"go.uber.org/zap"
 )
 
 func main() {
diff --git a/pkg/midi/in.go b/pkg/midi/in.go
index 5964be7..403cfad 100644
--- a/pkg/midi/in.go
+++ b/pkg/midi/in.go
@@ -10,7 +10,6 @@ import (
 
 func (md *MidiDevice) sendSignal(signal core.Signal) {
 	if signal != nil {
-		log.Println(signal.Code(), signal)
 		md.signals <- signal
 	}
 }
diff --git a/pkg/midi/midi_device.go b/pkg/midi/midi_device.go
index 607717b..7022587 100644
--- a/pkg/midi/midi_device.go
+++ b/pkg/midi/midi_device.go
@@ -2,14 +2,15 @@ package midi
 
 import (
 	"fmt"
-	"git.miem.hse.ru/hubman/hubman-lib/core"
-	"gitlab.com/gomidi/midi/v2"
-	"gitlab.com/gomidi/midi/v2/drivers"
 	"midi_manipulator/pkg/backlight"
 	"midi_manipulator/pkg/config"
 	"midi_manipulator/pkg/model"
 	"sync"
 	"time"
+
+	"git.miem.hse.ru/hubman/hubman-lib/core"
+	"gitlab.com/gomidi/midi/v2"
+	"gitlab.com/gomidi/midi/v2/drivers"
 	"go.uber.org/zap"
 )
 
@@ -79,11 +80,11 @@ func (md *MidiDevice) reconnect(backlightConfig *backlight.DecodedDeviceBackligh
 			err := md.connectDevice()
 
 			if err == nil {
+				md.logger.Warn("Device reconnected", zap.String("alias", md.name))
 				md.mutex.Unlock()
 				time.Sleep(4000 * time.Millisecond)
 				md.startupIllumination(backlightConfig)
 				go md.listen()
-				md.logger.Warn("Device reconnected", zap.String("alias", md.name))
 				continue
 			}
 		}
-- 
GitLab


From 3465a2a1ae8620a63825098c99f13783ec9cea8c Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Sat, 27 Jan 2024 00:14:00 +0300
Subject: [PATCH 5/9] added debug logs for signals

---
 pkg/midi/in.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/pkg/midi/in.go b/pkg/midi/in.go
index 403cfad..ecb3200 100644
--- a/pkg/midi/in.go
+++ b/pkg/midi/in.go
@@ -6,10 +6,12 @@ import (
 	"log"
 	"midi_manipulator/pkg/model"
 	"time"
+	"go.uber.org/zap"
 )
 
 func (md *MidiDevice) sendSignal(signal core.Signal) {
 	if signal != nil {
+		md.logger.Debug("", zap.String("signal", string(signal.Code())), zap.Any("payload", signal))
 		md.signals <- signal
 	}
 }
-- 
GitLab


From 0bdd8561cff97726db43ceb72b7bf522f4398a40 Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Sat, 27 Jan 2024 11:01:20 +0300
Subject: [PATCH 6/9] added log msg

---
 pkg/midi/in.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkg/midi/in.go b/pkg/midi/in.go
index ecb3200..12b3e12 100644
--- a/pkg/midi/in.go
+++ b/pkg/midi/in.go
@@ -11,7 +11,7 @@ import (
 
 func (md *MidiDevice) sendSignal(signal core.Signal) {
 	if signal != nil {
-		md.logger.Debug("", zap.String("signal", string(signal.Code())), zap.Any("payload", signal))
+		md.logger.Debug("Received signal from MIDI device", zap.String("signal", string(signal.Code())), zap.Any("payload", signal))
 		md.signals <- signal
 	}
 }
-- 
GitLab


From 413a3b5e0ea690dd3762f61f877ad4c506a981be Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Sat, 27 Jan 2024 11:59:06 +0300
Subject: [PATCH 7/9] added startup_delay field to config, fixed type for
 hold_delta

---
 configs/backlight_config.yaml |  8 +++----
 configs/user_config.yaml      |  1 +
 pkg/config/config.go          | 17 ++++++++++-----
 pkg/midi/midi_device.go       | 41 ++++++++++++++++++++---------------
 4 files changed, 40 insertions(+), 27 deletions(-)

diff --git a/configs/backlight_config.yaml b/configs/backlight_config.yaml
index d7d813e..0fcf641 100644
--- a/configs/backlight_config.yaml
+++ b/configs/backlight_config.yaml
@@ -170,11 +170,11 @@ device_light_configuration:
         statuses:
           on:
             type: Sysex
-            fallback_color: red
+            fallback_color: white
             bytes: F0 47 7F 43 65 00 04 %key %payload F7
           off:
             type: Sysex
-            fallback_color: light_red
+            fallback_color: light_white
             bytes: F0 47 7F 43 65 00 04 %key %payload F7
       - key_range:
           - 70
@@ -184,11 +184,11 @@ device_light_configuration:
         statuses:
           on:
             type: Sysex
-            fallback_color: green
+            fallback_color: white
             bytes: F0 47 7F 43 65 00 04 %key %payload F7
           off:
             type: Sysex
-            fallback_color: light_green
+            fallback_color: light_white
             bytes: F0 47 7F 43 65 00 04 %key %payload F7
       - key_range:
           - 86
diff --git a/configs/user_config.yaml b/configs/user_config.yaml
index 9464b39..9e4b94f 100644
--- a/configs/user_config.yaml
+++ b/configs/user_config.yaml
@@ -1,5 +1,6 @@
 midi_devices:
   - device_name: MPD226
+    startup_delay: 1000
     active: true
     hold_delta: 1000
     namespace: default
diff --git a/pkg/config/config.go b/pkg/config/config.go
index b02ff51..b18f21e 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -19,11 +19,12 @@ type Controls struct {
 }
 
 type DeviceConfig struct {
-	DeviceName string   `json:"device_name" yaml:"device_name"`
-	Active     bool     `json:"active" yaml:"active"`
-	HoldDelta  float64  `json:"hold_delta" yaml:"hold_delta"`
-	Namespace  string   `json:"namespace" yaml:"namespace"`
-	Controls   Controls `json:"accumulate_controls" yaml:"accumulate_controls"`
+	DeviceName   string   `json:"device_name" yaml:"device_name"`
+	StartupDelay int      `json:"startup_delay" yaml:"startup_delay"`
+	Active       bool     `json:"active" yaml:"active"`
+	HoldDelta    int      `json:"hold_delta" yaml:"hold_delta"`
+	Namespace    string   `json:"namespace" yaml:"namespace"`
+	Controls     Controls `json:"accumulate_controls" yaml:"accumulate_controls"`
 }
 
 type UserConfig struct {
@@ -53,6 +54,12 @@ func (conf *UserConfig) Validate() error {
 				" Now {%f} is provided",
 				idx, device.DeviceName, device.HoldDelta)
 		}
+		if device.StartupDelay < 0 {
+			return fmt.Errorf("device #{%d} ({%s}): "+
+				"valid MIDI startup_delay must be provided in config."+
+				" Now {%d} is provided",
+				idx, device.DeviceName, device.StartupDelay)
+		}
 	}
 	return nil
 }
diff --git a/pkg/midi/midi_device.go b/pkg/midi/midi_device.go
index 7022587..6b45e4e 100644
--- a/pkg/midi/midi_device.go
+++ b/pkg/midi/midi_device.go
@@ -14,19 +14,22 @@ import (
 	"go.uber.org/zap"
 )
 
+var DEVICE_RECONNECT_INTERVAL = 2000 * time.Millisecond
+
 type MidiDevice struct {
-	name        string
-	active      bool
-	ports       MidiPorts
-	clickBuffer ClickBuffer
-	holdDelta   time.Duration
-	mutex       sync.Mutex
-	stop        chan bool
-	namespace   string
-	connected   bool
-	controls    map[byte]*Control
-	signals     chan<- core.Signal
-	logger      *zap.Logger
+	name         string
+	active       bool
+	ports        MidiPorts
+	clickBuffer  ClickBuffer
+	holdDelta    time.Duration
+	startupDelay time.Duration
+	mutex        sync.Mutex
+	stop         chan bool
+	namespace    string
+	connected    bool
+	controls     map[byte]*Control
+	signals      chan<- core.Signal
+	logger       *zap.Logger
 }
 
 type MidiPorts struct {
@@ -60,6 +63,7 @@ func (md *MidiDevice) StopDevice() error {
 }
 
 func (md *MidiDevice) RunDevice(backlightConfig *backlight.DecodedDeviceBacklightConfig) error {
+	time.Sleep(md.startupDelay)
 	md.startupIllumination(backlightConfig)
 	go md.listen()
 	go md.reconnect(backlightConfig)
@@ -82,7 +86,7 @@ func (md *MidiDevice) reconnect(backlightConfig *backlight.DecodedDeviceBackligh
 			if err == nil {
 				md.logger.Warn("Device reconnected", zap.String("alias", md.name))
 				md.mutex.Unlock()
-				time.Sleep(4000 * time.Millisecond)
+				time.Sleep(md.startupDelay)
 				md.startupIllumination(backlightConfig)
 				go md.listen()
 				continue
@@ -90,7 +94,7 @@ func (md *MidiDevice) reconnect(backlightConfig *backlight.DecodedDeviceBackligh
 		}
 
 		md.mutex.Unlock()
-		time.Sleep(2000 * time.Millisecond)
+		time.Sleep(DEVICE_RECONNECT_INTERVAL)
 	}
 }
 
@@ -142,7 +146,8 @@ func (md *MidiDevice) connectInPort() error {
 func (md *MidiDevice) applyConfiguration(deviceConfig config.DeviceConfig, signals chan<- core.Signal, logger *zap.Logger) {
 	md.name = deviceConfig.DeviceName
 	md.active = deviceConfig.Active
-	md.holdDelta = time.Duration(float64(time.Millisecond) * deviceConfig.HoldDelta)
+	md.holdDelta = time.Duration(int(time.Millisecond) * deviceConfig.HoldDelta)
+	md.startupDelay = time.Duration(int(time.Millisecond) * deviceConfig.StartupDelay)
 	md.clickBuffer = make(map[uint8]*KeyContext)
 	md.stop = make(chan bool, 1)
 	md.namespace = deviceConfig.Namespace
@@ -171,7 +176,7 @@ func (md *MidiDevice) updateConfiguration(config config.DeviceConfig) {
 	md.mutex.Lock()
 	defer md.mutex.Unlock()
 	md.active = config.Active
-	md.holdDelta = time.Duration(float64(time.Millisecond) * config.HoldDelta)
+	md.holdDelta = time.Duration(int(time.Millisecond) * config.HoldDelta)
 	if md.namespace != config.Namespace {
 		var oldNamespace = md.namespace
 		md.namespace = config.Namespace
@@ -189,9 +194,9 @@ func NewDevice(deviceConfig config.DeviceConfig, signals chan<- core.Signal, log
 	return &midiDevice, err
 }
 
-func (md *MidiDevice) sendNamespaceChangedSignal(signals chan<- core.Signal, oldNamespace string, newNamespace string){
+func (md *MidiDevice) sendNamespaceChangedSignal(signals chan<- core.Signal, oldNamespace string, newNamespace string) {
 	signal := model.NamespaceChanged{
-		Device: md.name,
+		Device:       md.name,
 		OldNamespace: oldNamespace,
 		NewNamespace: newNamespace,
 	}
-- 
GitLab


From 5451bcab8c0ee88e4417997adf9ec9b4a92e4410 Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Sat, 27 Jan 2024 12:01:02 +0300
Subject: [PATCH 8/9] fixed config validation errors

---
 pkg/config/config.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/pkg/config/config.go b/pkg/config/config.go
index b18f21e..a397c8f 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -42,8 +42,8 @@ func (conf *UserConfig) Validate() error {
 		if device.DeviceName == "" {
 			return fmt.Errorf("device #{%d} ({%s}): "+
 				"valid MIDI device_name must be provided in config. "+
-				"Now {%f} is provided",
-				idx, device.DeviceName, device.HoldDelta)
+				"Now {%s} is provided",
+				idx, device.DeviceName, device.DeviceName)
 		}
 		if device.Namespace == "" {
 			return fmt.Errorf("device #{%d} ({%s}) has no namespace specified", idx, device.DeviceName)
@@ -51,7 +51,7 @@ func (conf *UserConfig) Validate() error {
 		if device.HoldDelta < 0 {
 			return fmt.Errorf("device #{%d} ({%s}): "+
 				"valid MIDI hold_delta must be provided in config."+
-				" Now {%f} is provided",
+				" Now {%d} is provided",
 				idx, device.DeviceName, device.HoldDelta)
 		}
 		if device.StartupDelay < 0 {
-- 
GitLab


From 6684ef4f654ceb81c5a74f063d1a1155e34a4994 Mon Sep 17 00:00:00 2001
From: Alexey Efremov <amefremov@edu.hse.ru>
Date: Sat, 27 Jan 2024 12:47:21 +0300
Subject: [PATCH 9/9] added configurable reconnect_interval, resolved race
 conditions for time delays

---
 configs/user_config.yaml |  1 +
 pkg/config/config.go     | 19 +++++++++++++------
 pkg/midi/midi_device.go  | 38 +++++++++++++++++++++-----------------
 3 files changed, 35 insertions(+), 23 deletions(-)

diff --git a/configs/user_config.yaml b/configs/user_config.yaml
index 9e4b94f..682ab47 100644
--- a/configs/user_config.yaml
+++ b/configs/user_config.yaml
@@ -1,6 +1,7 @@
 midi_devices:
   - device_name: MPD226
     startup_delay: 1000
+    reconnect_interval: 2000
     active: true
     hold_delta: 1000
     namespace: default
diff --git a/pkg/config/config.go b/pkg/config/config.go
index a397c8f..47de12a 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -19,12 +19,13 @@ type Controls struct {
 }
 
 type DeviceConfig struct {
-	DeviceName   string   `json:"device_name" yaml:"device_name"`
-	StartupDelay int      `json:"startup_delay" yaml:"startup_delay"`
-	Active       bool     `json:"active" yaml:"active"`
-	HoldDelta    int      `json:"hold_delta" yaml:"hold_delta"`
-	Namespace    string   `json:"namespace" yaml:"namespace"`
-	Controls     Controls `json:"accumulate_controls" yaml:"accumulate_controls"`
+	DeviceName        string   `json:"device_name" yaml:"device_name"`
+	StartupDelay      int      `json:"startup_delay" yaml:"startup_delay"`
+	ReconnectInterval int      `json:"reconnect_interval" yaml:"reconnect_interval"`
+	Active            bool     `json:"active" yaml:"active"`
+	HoldDelta         int      `json:"hold_delta" yaml:"hold_delta"`
+	Namespace         string   `json:"namespace" yaml:"namespace"`
+	Controls          Controls `json:"accumulate_controls" yaml:"accumulate_controls"`
 }
 
 type UserConfig struct {
@@ -60,6 +61,12 @@ func (conf *UserConfig) Validate() error {
 				" Now {%d} is provided",
 				idx, device.DeviceName, device.StartupDelay)
 		}
+		if device.ReconnectInterval < 2 {
+			return fmt.Errorf("device #{%d} ({%s}): "+
+				"valid MIDI reconnect_interval must be provided in config (> 2000ms)."+
+				" Now {%d} is provided",
+				idx, device.DeviceName, device.ReconnectInterval)
+		}
 	}
 	return nil
 }
diff --git a/pkg/midi/midi_device.go b/pkg/midi/midi_device.go
index 6b45e4e..541aeff 100644
--- a/pkg/midi/midi_device.go
+++ b/pkg/midi/midi_device.go
@@ -14,22 +14,21 @@ import (
 	"go.uber.org/zap"
 )
 
-var DEVICE_RECONNECT_INTERVAL = 2000 * time.Millisecond
-
 type MidiDevice struct {
-	name         string
-	active       bool
-	ports        MidiPorts
-	clickBuffer  ClickBuffer
-	holdDelta    time.Duration
-	startupDelay time.Duration
-	mutex        sync.Mutex
-	stop         chan bool
-	namespace    string
-	connected    bool
-	controls     map[byte]*Control
-	signals      chan<- core.Signal
-	logger       *zap.Logger
+	name              string
+	active            bool
+	ports             MidiPorts
+	clickBuffer       ClickBuffer
+	holdDelta         time.Duration
+	startupDelay      time.Duration
+	reconnectInterval time.Duration
+	mutex             sync.Mutex
+	stop              chan bool
+	namespace         string
+	connected         bool
+	controls          map[byte]*Control
+	signals           chan<- core.Signal
+	logger            *zap.Logger
 }
 
 type MidiPorts struct {
@@ -85,16 +84,19 @@ func (md *MidiDevice) reconnect(backlightConfig *backlight.DecodedDeviceBackligh
 
 			if err == nil {
 				md.logger.Warn("Device reconnected", zap.String("alias", md.name))
+				var startupDelay = md.startupDelay
 				md.mutex.Unlock()
-				time.Sleep(md.startupDelay)
+
+				time.Sleep(startupDelay)
 				md.startupIllumination(backlightConfig)
 				go md.listen()
 				continue
 			}
 		}
 
+		var reconnectInterval = md.reconnectInterval
 		md.mutex.Unlock()
-		time.Sleep(DEVICE_RECONNECT_INTERVAL)
+		time.Sleep(reconnectInterval)
 	}
 }
 
@@ -148,6 +150,7 @@ func (md *MidiDevice) applyConfiguration(deviceConfig config.DeviceConfig, signa
 	md.active = deviceConfig.Active
 	md.holdDelta = time.Duration(int(time.Millisecond) * deviceConfig.HoldDelta)
 	md.startupDelay = time.Duration(int(time.Millisecond) * deviceConfig.StartupDelay)
+	md.reconnectInterval = time.Duration(int(time.Millisecond) * deviceConfig.ReconnectInterval)
 	md.clickBuffer = make(map[uint8]*KeyContext)
 	md.stop = make(chan bool, 1)
 	md.namespace = deviceConfig.Namespace
@@ -177,6 +180,7 @@ func (md *MidiDevice) updateConfiguration(config config.DeviceConfig) {
 	defer md.mutex.Unlock()
 	md.active = config.Active
 	md.holdDelta = time.Duration(int(time.Millisecond) * config.HoldDelta)
+	md.reconnectInterval = time.Duration(int(time.Millisecond) * config.ReconnectInterval)
 	if md.namespace != config.Namespace {
 		var oldNamespace = md.namespace
 		md.namespace = config.Namespace
-- 
GitLab