diff --git a/devices/audio-controller/audio_controller.go b/devices/audio-controller/audio_controller.go
index 10452f39a5af3f2eb27fe7d9d31eb7ec61df7822..ce956a573c72990310ccde19342d52cfb004feec 100644
--- a/devices/audio-controller/audio_controller.go
+++ b/devices/audio-controller/audio_controller.go
@@ -2,91 +2,137 @@ package main
 
 import (
 	"encoding/json"
+	"flag"
 	"fmt"
 	"time"
 
 	"code.nap.av.it.pt/go-device/device"
 	"code.nap.av.it.pt/go-device/listener"
 	"code.nap.av.it.pt/go-device/sender"
+	"code.nap.av.it.pt/go-device/utils"
 )
 
+var regSuccess = make(chan bool, 1)
+var deviceID string
+
 // AudioController represents an audio device that can be started and stopped.
 type AudioController struct {
 	device device.Device
 }
 
-func handleAudioMessage(msg map[string]interface{}) error {
+// handleAudioMessage handles audio messages.
+func handleAudioMessage(msg map[string]interface{}, args []interface{}) error {
 	// Print the received message.
 	fmt.Println("Received message:", msg)
 
-	// Handle the audio message.
-	// command, ok := args["command"].(string)
-	// if !ok {
-	// 	return fmt.Errorf("command not found or not a string")
-	// }
-
-	// switch command {
-	// case "play":
-	// 	fileName, ok := args["file_name"].(string)
-	// 	if !ok {
-	// 		return fmt.Errorf("file_name not found or not a string")
-	// 	}
-	// 	// Add logic to play the file
-	// 	fmt.Printf("Playing file: %s\n", fileName)
-	// case "pause":
-	// 	// Add logic to pause playback
-	// 	fmt.Println("Pausing playback")
-	// case "resume":
-	// 	// Add logic to resume playback
-	// 	fmt.Println("Resuming playback")
-	// case "stop":
-	// 	// Add logic to stop playback
-	// 	fmt.Println("Stopping playback")
-	// default:
-	// 	return fmt.Errorf("unknown command: %s", command)
-	// }
+	switch msg["address"] {
+
+	case fmt.Sprintf("/register/%s", deviceID):
+		// Cast the message argument to string.
+		result, ok := args[0].(string)
+		if !ok {
+			return fmt.Errorf("result not found or not a string")
+		}
+
+		// Check if the registration was successful.
+		if result == "success" {
+			regSuccess <- true
+		}
+		fmt.Println("Received registration result:", result)
+
+	case "/command/audio/":
+		// Unmarshal the message arguments.
+		var info map[string]interface{}
+		err := json.Unmarshal(args[0].([]byte), &info)
+		if err != nil {
+			fmt.Println("Error unmarshalling message arguments:", err)
+			return err
+		}
+
+		// Handle the audio message.
+		command, ok := info["action"].(string)
+		if !ok {
+			return fmt.Errorf("command not found or not a string")
+		}
+
+		switch command {
+		case "play":
+			fileName, ok := info["file"].(string)
+			if !ok {
+				return fmt.Errorf("file_name not found or not a string")
+			}
+			// Add logic to play the file
+			fmt.Printf("Playing file: %s\n", fileName)
+		case "pause":
+			// Add logic to pause playback
+			fmt.Println("Pausing playback")
+		case "resume":
+			// Add logic to resume playback
+			fmt.Println("Resuming playback")
+		case "stop":
+			// Add logic to stop playback
+			fmt.Println("Stopping playback")
+		default:
+			return fmt.Errorf("unknown command: %s", command)
+		}
+	}
+
 	return nil
 }
 
 func main() {
+
+	// Get config file from the command line parameters.
+	configFile := flag.String("config", "", "Path to the config file")
+	flag.Parse()
+
+	// Check if the config file is provided.
+	if *configFile == "" {
+		fmt.Println("Please provide a config file")
+		return
+	}
+
+	// Read config file to get device information.
+	config, err := utils.ReadConfigFile(*configFile)
+	if err != nil {
+		fmt.Println("Error reading config file:", err)
+		return
+	}
+
 	// Create a new audio device.
 	audioController := &AudioController{
+		// TODO: pass config to the NewDevice function
 		device: device.NewDevice(
-			device.Actuator,
-			listener.NewListener(listener.OSC, handleAudioMessage, "127.0.0.1:8000"),
-			sender.NewSender(sender.OSC, "localhost", 8002),
+			config,
+			listener.NewListener(listener.OSC, handleAudioMessage, fmt.Sprintf("%s:%d", config.DeviceIP, config.DevicePort)),
+			sender.NewSender(sender.OSC, config.ServerIP, config.ServerPort),
 		),
 	}
 
 	// Start the audio controller.
-	err := audioController.device.Start()
+	err = audioController.device.Start()
 	if err != nil {
 		fmt.Printf("Failed to start audio controller: %v\n", err)
 		return
 	}
 	fmt.Println("Audio controller started")
 
-	// Device info map
-	// TODO: Replace with actual device information (from config file).
-	infoMap := map[string]string{
-		"deviceID":   "1",
-		"deviceType": "speaker",
-		"deviceName": "Speaker 1",
-		"deviceIP":   "localhost",
-		"devicePort": "8002",
-	}
+	// Set the device ID.
+	deviceID = config.DeviceID
 
-	// Advertisement message.
-	infoJson, err := json.Marshal(infoMap)
+	// Sleep for a while to allow the audio controller to start.
+	time.Sleep(2 * time.Second)
+
+	// TODO: Add logic to handle registration and heartbeat messages.
+	// Register the device.
+	err = audioController.device.Register(regSuccess)
 	if err != nil {
-		fmt.Println("Error marshalling map to JSON:", err)
+		fmt.Printf("Failed to register device: %v\n", err)
 		return
 	}
 
-	audioController.device.Send(
-		"/test",
-		[]byte(string(infoJson)),
-	)
+	// TODO: Add logic to guarantee that the device is registered before sending heartbeat messages.
+	// Add a number of retries and a timeout for the registration process in the config.
 
 	for {
 		// Add logic to handle other operations.
@@ -95,6 +141,9 @@ func main() {
 		time.Sleep(1 * time.Second)
 
 		// Send an OSC message to the tour controller device.
-		audioController.device.Send("/heartbeat", infoMap["deviceID"])
+		audioController.device.Send("/heartbeat", config.DeviceID)
 	}
+
+	// TODO: Add logic to handle stop signal and stop the audio controller.
+	// https://gobyexample.com/signals
 }
diff --git a/devices/audio-device/audio_device.go b/devices/audio-device/audio_device.go
new file mode 100644
index 0000000000000000000000000000000000000000..b700255a041539d56873eda26ddf62c113b641f7
--- /dev/null
+++ b/devices/audio-device/audio_device.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"time"
+
+	"code.nap.av.it.pt/go-device/device"
+	"code.nap.av.it.pt/go-device/listener"
+	"code.nap.av.it.pt/go-device/sender"
+	"code.nap.av.it.pt/go-device/utils"
+)
+
+func messageCallback(msg map[string]interface{}, args []interface{}) {
+	fmt.Println("Received message:", msg)
+	fmt.Println("Topic:", msg["address"])
+}
+
+func main() {
+	// Get config file from the command line parameters.
+	configFile := flag.String("config", "", "Path to the config file")
+	flag.Parse()
+
+	// Check if the config file is provided.
+	if *configFile == "" {
+		fmt.Println("Please provide a config file")
+		return
+	}
+
+	// Read config file to get device information.
+	config, err := utils.ReadConfigFile(*configFile)
+	if err != nil {
+		fmt.Println("Error reading config file:", err)
+		return
+	}
+
+	// Create device
+	audioDevice := device.NewDevice(
+		config,
+		listener.OSC,
+		sender.OSC,
+		messageCallback,
+	)
+
+	// Start the device
+	err = audioDevice.Start()
+	if err != nil {
+		fmt.Println("Error starting device:", err)
+		return
+	}
+
+	// Sleep for 2 seconds to allow the device to start.
+	time.Sleep(2 * time.Second)
+
+	// Register the device
+	err = audioDevice.Register()
+	if err != nil {
+		fmt.Println("Error registering device:", err)
+		return
+	}
+
+	// Wait until the device is registered
+	for !audioDevice.IsRegistered() {
+		time.Sleep(1 * time.Second)
+	}
+
+	// Loop lifecycle
+	for {
+		// Send heartbeat
+		err = audioDevice.SendHeartbeat()
+		if err != nil {
+			fmt.Println("Error sending heartbeat:", err)
+			return
+		}
+
+		// DO SOMETHING
+
+		// Sleep for 5 seconds
+		time.Sleep(5 * time.Second)
+	}
+}
diff --git a/devices/audio-device/go.mod b/devices/audio-device/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..da73dacf80453934163ddab9e4a3a14e01599f50
--- /dev/null
+++ b/devices/audio-device/go.mod
@@ -0,0 +1,7 @@
+module audio_device
+
+go 1.22.2
+
+replace code.nap.av.it.pt/go-device => ../go-device
+
+require code.nap.av.it.pt/go-device v0.0.0-00010101000000-000000000000 // indirect
diff --git a/devices/configs/audio/audio1.json b/devices/configs/audio/audio1.json
new file mode 100644
index 0000000000000000000000000000000000000000..37587e3e9e299efa9e3b098a440fed14ca520b15
--- /dev/null
+++ b/devices/configs/audio/audio1.json
@@ -0,0 +1,13 @@
+{
+    "server_ip": "127.0.0.1",
+    "server_port": 8000,
+    "device_id": "audio_device_1",
+    "device_type": "actuator",
+    "device_family": "audio",
+    "device_name": "Audio Device 1",
+    "device_ip": "127.0.0.1",
+    "device_port": 9090,
+    "addresses": [
+        "/command/audio/*"
+    ]
+}
\ No newline at end of file
diff --git a/devices/configs/audio/audio2.json b/devices/configs/audio/audio2.json
new file mode 100644
index 0000000000000000000000000000000000000000..330f123e46ebe92a2d69a436c1af7edb59c03086
--- /dev/null
+++ b/devices/configs/audio/audio2.json
@@ -0,0 +1,13 @@
+{
+    "server_ip": "127.0.0.1",
+    "server_port": 8000,
+    "device_id": "audio_device_2",
+    "device_type": "actuator",
+    "device_family": "audio",
+    "device_name": "Audio Device 2",
+    "device_ip": "127.0.0.1",
+    "device_port": 9091,
+    "addresses": [
+        "/command/audio/*"
+    ]
+}
\ No newline at end of file
diff --git a/devices/configs/tour/tour1.json b/devices/configs/tour/tour1.json
new file mode 100644
index 0000000000000000000000000000000000000000..9108bb3ae08d7b05d9db9914dc629e6fb50f21f3
--- /dev/null
+++ b/devices/configs/tour/tour1.json
@@ -0,0 +1,13 @@
+{
+    "server_ip": "10.42.0.187",
+    "server_port": 9000,
+    "device_id": "controller_1",
+    "device_type": "actuator",
+    "device_family": "controller",
+    "device_name": "Tour Controller 1",
+    "device_ip": "10.42.0.1",
+    "device_port": 9000,
+    "addresses": [
+        
+    ]
+}
\ No newline at end of file
diff --git a/devices/go-device/controller/controller.go b/devices/go-device/controller/controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..af48966d7429b95ea7a30f0a5b74e3dab584a7e0
--- /dev/null
+++ b/devices/go-device/controller/controller.go
@@ -0,0 +1,13 @@
+package controller
+
+type Controller interface {
+	Start() error
+	Stop() error
+	Send(topic string, message interface{}) error
+	SendTo(topic string, message interface{}, deviceID string) error
+	SendHeartbeat() error
+}
+
+func NewController() Controller {
+	return nil
+}
diff --git a/devices/go-device/device/device.go b/devices/go-device/device/device.go
index 022cbded260e3372361f81724cf0e219a710dfbf..101ccf468cbda1b241390c927917921b53fc2299 100644
--- a/devices/go-device/device/device.go
+++ b/devices/go-device/device/device.go
@@ -3,8 +3,14 @@
 package device
 
 import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
 	"code.nap.av.it.pt/go-device/listener"
 	"code.nap.av.it.pt/go-device/sender"
+	"code.nap.av.it.pt/go-device/utils"
 )
 
 // TODO: Docs
@@ -26,6 +32,19 @@ func (t DeviceType) String() string {
 	return deviceTypeNames[t]
 }
 
+func getDeviceType(deviceType string) (DeviceType, error) {
+	switch deviceType {
+	case "actuator":
+		return Actuator, nil
+	case "sensor":
+		return Sensor, nil
+	case "unity":
+		return Unity, nil
+	default:
+		return -1, errors.New("unknown device type")
+	}
+}
+
 // Device represents a device that can be started and stopped.
 type Device interface {
 	// Start starts the device.
@@ -34,24 +53,97 @@ type Device interface {
 	Stop() error
 	// Send sends a message to a topic.
 	Send(topic string, message interface{}) error
+	// Register registers the device.
+	Register() error
+	// IsRegistered returns true if the device is registered.
+	IsRegistered() bool
+	// SendHeartbeat sends a heartbeat message.
+	SendHeartbeat() error
 }
 
 type device struct {
-	Type     DeviceType
-	Listener listener.Listener
-	Sender   sender.Sender
+	Config    utils.DeviceConfig
+	Type      DeviceType
+	Listener  listener.Listener
+	Sender    sender.Sender
+	Callback  utils.DeviceCallback
+	Heartbeat Heartbeat
+}
+
+type Heartbeat struct {
+	Registered bool
+	LastSent   time.Time
+	LastRecv   time.Time
+}
+
+func NewDevice(c utils.DeviceConfig,
+	listenerType listener.ListenerType, senderType sender.SenderType, f utils.DeviceCallback) Device {
+
+	devType, err := getDeviceType(c.DeviceType)
+	if err != nil {
+		// TODO: Log error
+		fmt.Println("Error getting device type:", err)
+		return nil
+	}
+
+	dev := &device{
+		Config:   c,
+		Type:     devType,
+		Callback: f,
+		Heartbeat: Heartbeat{
+			Registered: false,
+		},
+	}
+
+	l := listener.NewListener(listener.OSC, dev.msgHandler, fmt.Sprintf("%s:%d", c.DeviceIP, c.DevicePort))
+	s := sender.NewSender(sender.OSC, c.ServerIP, c.ServerPort)
+
+	dev.Listener = l
+	dev.Sender = s
+
+	return dev
 }
 
-func NewDevice(t DeviceType, l listener.Listener, s sender.Sender) Device {
-	return &device{Type: t, Listener: l, Sender: s}
+func (d *device) msgHandler(msg map[string]interface{}, args []interface{}) {
+
+	fmt.Println("received message")
+
+	switch msg["address"] {
+
+	case fmt.Sprintf("/register/%s", d.Config.DeviceID):
+		// Cast the message argument to string.
+		result, ok := args[0].(string)
+		if !ok {
+			fmt.Println("result not found or not a string")
+		}
+
+		// Check if the registration was successful.
+		if result == "success" {
+			d.Heartbeat.Registered = true
+		}
+
+		fmt.Println("Received registration result:", result)
+
+	case "/heartbeat":
+		fmt.Println("Received heartbeat message")
+		d.Heartbeat.LastRecv = time.Now()
+
+	default:
+		d.Callback(msg, args)
+	}
 }
 
 func (d *device) Start() error {
-	err := d.Listener.Listen()
+	addresses := []string{"/register", "/heartbeat", "/command/audio/"}
+	err := d.Listener.Listen(addresses)
 	if err != nil {
 		return err
 	}
 
+	if d.Sender == nil {
+		return nil
+	}
+
 	return d.Sender.Connect()
 }
 
@@ -60,5 +152,43 @@ func (d *device) Stop() error {
 }
 
 func (d *device) Send(topic string, message interface{}) error {
+	if d.Sender == nil {
+		return errors.New("no sender available")
+	}
+
 	return d.Sender.Publish(topic, message)
 }
+
+func (d *device) Register() error {
+	// Device info map
+	infoMap := map[string]string{
+		"device_id":     d.Config.DeviceID,
+		"device_type":   d.Config.DeviceType,
+		"device_family": d.Config.DeviceFamily,
+		"device_name":   d.Config.DeviceName,
+		"device_ip":     d.Config.DeviceIP,
+		"device_port":   fmt.Sprintf("%d", d.Config.DevicePort),
+	}
+
+	// Advertisement message.
+	infoJson, err := json.Marshal(infoMap)
+	if err != nil {
+		return fmt.Errorf("error marshalling map to JSON: %s", err)
+	}
+
+	d.Send(
+		"/register",
+		[]byte(string(infoJson)),
+	)
+
+	return nil
+}
+
+func (d *device) IsRegistered() bool {
+	return d.Heartbeat.Registered
+}
+
+func (d *device) SendHeartbeat() error {
+	// send heartbeat
+	return d.Send("/heartbeat", []byte("ola"))
+}
diff --git a/devices/go-device/device/doc.go b/devices/go-device/device/doc.go
deleted file mode 100644
index 76a9bfa320f3702064d15b3b279c0ad6a3b27c51..0000000000000000000000000000000000000000
--- a/devices/go-device/device/doc.go
+++ /dev/null
@@ -1 +0,0 @@
-package device
diff --git a/devices/go-device/listener/listener.go b/devices/go-device/listener/listener.go
index fde01e483f19c248233dffc8ac4b85392de65c67..379ece825546ec9914905fc1200a4364baacb750 100644
--- a/devices/go-device/listener/listener.go
+++ b/devices/go-device/listener/listener.go
@@ -22,7 +22,7 @@ func (t ListenerType) String() string {
 
 type Listener interface {
 	// Listen starts the listener.
-	Listen() error
+	Listen(topics []string) error
 	// Close stops the listener.
 	Close() error
 	// Subscribe subscribes to a topic.
diff --git a/devices/go-device/listener/listeners/osc/osc_listener.go b/devices/go-device/listener/listeners/osc/osc_listener.go
index e32b3b757fdec6c51bd3db38035998936309e41f..7578f31e5871873de7efbaae20378bed82f2defc 100644
--- a/devices/go-device/listener/listeners/osc/osc_listener.go
+++ b/devices/go-device/listener/listeners/osc/osc_listener.go
@@ -1,7 +1,9 @@
 package osc
 
 import (
+	"fmt"
 	"log"
+	"strings"
 
 	"code.nap.av.it.pt/go-device/utils"
 	"github.com/hypebeast/go-osc/osc"
@@ -16,7 +18,7 @@ type OSCListener struct {
 }
 
 // Listen starts the OSC listener.
-func (l *OSCListener) Listen() error {
+func (l *OSCListener) Listen(addresses []string) error {
 	// Create a new OSC server.
 
 	// 1. Check if config is valid
@@ -24,7 +26,6 @@ func (l *OSCListener) Listen() error {
 
 	// 2. Get addresses from config
 	// TODO: Get addresses from config
-	addresses := []string{"/test", "/heartbeat"}
 
 	// 3. Create a new dispatcher
 	dispatcher := osc.NewStandardDispatcher()
@@ -34,12 +35,27 @@ func (l *OSCListener) Listen() error {
 		// create a message handler for the address
 		err := dispatcher.AddMsgHandler(
 			address, func(msg *osc.Message) {
-				args, err := utils.Mapify(msg)
+				// Create a map to store the message info
+				msg_info := make(map[string]interface{})
+
+				// Add the address to the msg_info
+				msg_info["address"] = msg.Address
+
+				// Add the number of arguments to the msg_info
+				msg_info["count"] = len(msg.Arguments)
+
+				// Add the argument types to the msg_info
+				types_str, err := msg.TypeTags()
 				if err != nil {
-					log.Fatalf("Error mapping OSC message: %v", msg)
+					fmt.Println("Error getting type tags", err)
+					return
 				}
 
-				l.HandlerFunc(args)
+				types := strings.Split(types_str, "")
+
+				msg_info["types"] = types[1:]
+
+				l.HandlerFunc(msg_info, msg.Arguments)
 			},
 		)
 		if err != nil {
diff --git a/devices/go-device/utils/utils.go b/devices/go-device/utils/utils.go
index f0d5a2c996aa55cddf90dbf8d66c1fbcafb3c0c8..01cdaf357592580681cac65e1082221c11f61657 100644
--- a/devices/go-device/utils/utils.go
+++ b/devices/go-device/utils/utils.go
@@ -1,8 +1,23 @@
 package utils
 
-import "fmt"
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+)
 
-type DeviceCallback func(args map[string]interface{}) error
+type DeviceCallback func(msg map[string]interface{}, args []interface{})
+
+type DeviceConfig struct {
+	ServerIP     string `json:"server_ip"`
+	ServerPort   int    `json:"server_port"`
+	DeviceID     string `json:"device_id"`
+	DeviceType   string `json:"device_type"`
+	DeviceFamily string `json:"device_family"`
+	DeviceName   string `json:"device_name"`
+	DeviceIP     string `json:"device_ip"`
+	DevicePort   int    `json:"device_port"`
+}
 
 // Mapify converts an interface to a map with "field_name":value.
 func Mapify(args interface{}) (map[string]interface{}, error) {
@@ -14,3 +29,28 @@ func Mapify(args interface{}) (map[string]interface{}, error) {
 
 	return argsMap, nil
 }
+
+func ReadConfigFile(configFile string) (DeviceConfig, error) {
+	// Check if file exists
+	if _, err := os.Stat(configFile); os.IsNotExist(err) {
+		fmt.Println("Config file does not exist")
+		return DeviceConfig{}, err
+	}
+
+	// Read the config file
+	configData, err := os.ReadFile(configFile)
+	if err != nil {
+		fmt.Println("Error reading config file")
+		return DeviceConfig{}, err
+	}
+
+	// Unmarshal the config data
+	var config DeviceConfig
+	err = json.Unmarshal(configData, &config)
+	if err != nil {
+		fmt.Println("Error unmarshalling config data")
+		return DeviceConfig{}, err
+	}
+
+	return config, nil
+}
diff --git a/devices/tour-controller/tour_controller.go b/devices/tour-controller/tour_controller.go
index 6578f57a16a98d36cb627f6c2a6c300a9a7e4353..2a03f9610a5db1f3bcd6c10af593bbdfef040f01 100644
--- a/devices/tour-controller/tour_controller.go
+++ b/devices/tour-controller/tour_controller.go
@@ -2,71 +2,244 @@ package main
 
 import (
 	"encoding/json"
+	"flag"
 	"fmt"
+	"strconv"
 	"time"
 
 	"code.nap.av.it.pt/go-device/device"
 	"code.nap.av.it.pt/go-device/listener"
 	"code.nap.av.it.pt/go-device/sender"
+	"code.nap.av.it.pt/go-device/utils"
 )
 
-// type TourDevice struct {
-// 	deviceID   string
-// 	deviceType string
-// 	deviceName string
-// 	deviceIP   string
-// 	devicePort int
-// 	sender     sender.Sender
-// }
+type TourDevice struct {
+	deviceID   string
+	deviceType string
+	deviceName string
+	deviceIP   string
+	devicePort int
+	sender     sender.Sender
+}
 
 type TourController struct {
 	device device.Device
-	// devices []TourDevice
 }
 
-func handleMessage(msg map[string]interface{}) error {
-	fmt.Println("Received message:", msg)
-	return nil
+var devices = make([]TourDevice, 0)
+
+func handleMessage(msg map[string]interface{}, args []interface{}) {
+	switch msg["address"] {
+
+	case "/heartbeat":
+		// TODO: Implement heartbeat handling
+		fmt.Println("Received heartbeat from device", args[0])
+
+	case "/register":
+		// fmt.Println("Received test message with device info", string(args[0].([]byte)))
+
+		// Unmarshal the device info.
+		var deviceInfo map[string]interface{}
+		err := json.Unmarshal(args[0].([]byte), &deviceInfo)
+		if err != nil {
+			fmt.Println("Error unmarshalling device info:", err)
+		}
+
+		// Get and cast the device info.
+		deviceID, ok := deviceInfo["device_id"].(string)
+		if !ok {
+			fmt.Println("Device ID not found or not a string")
+		}
+
+		deviceType, ok := deviceInfo["device_type"].(string)
+		if !ok {
+			fmt.Println("Device type not found or not a string")
+		}
+
+		deviceName, ok := deviceInfo["device_name"].(string)
+		if !ok {
+			fmt.Println("Device name not found or not a string")
+		}
+
+		deviceIP, ok := deviceInfo["device_ip"].(string)
+		if !ok {
+			fmt.Println("Device IP not found or not a string")
+		}
+
+		devicePortStr, ok := deviceInfo["device_port"].(string)
+		if !ok {
+			fmt.Println("Device port not found or not a string")
+		}
+
+		devicePort, err := strconv.Atoi(devicePortStr)
+		if err != nil {
+			fmt.Println("Error converting device port to int:", err)
+		}
+
+		// Create a new sender
+		sender := sender.NewSender(sender.OSC, deviceIP, devicePort)
+
+		// Add the device to the list of devices.
+		devices = append(devices, TourDevice{
+			deviceID:   deviceID,
+			deviceType: deviceType,
+			deviceName: deviceName,
+			deviceIP:   deviceIP,
+			devicePort: devicePort,
+			sender:     sender,
+		})
+
+		// Send a response to the device.
+		err = sender.Publish("/register/"+deviceID, "success")
+		if err != nil {
+			fmt.Println("Error sending response to device:", err)
+		}
+
+		fmt.Println("Device registered:", deviceID)
+
+	default:
+		fmt.Println("Unknown message:", msg)
+	}
 }
 
 func main() {
-	tourController := &TourController{
-		device: device.NewDevice(
-			device.Actuator,
-			listener.NewListener(listener.OSC, handleMessage, "127.0.0.1:8002"),
-			sender.NewSender(sender.OSC, "localhost", 8000),
-		),
+	// Get config file from the command line parameters.
+	configFile := flag.String("config", "", "Path to the config file")
+	flag.Parse()
+
+	// Check if the config file is provided.
+	if *configFile == "" {
+		fmt.Println("Please provide a config file")
+		return
 	}
 
-	// Start the tour controller.
-	err := tourController.device.Start()
+	// Read config file to get device information.
+	config, err := utils.ReadConfigFile(*configFile)
 	if err != nil {
-		fmt.Println("Error starting the tour controller:", err)
+		fmt.Println("Error reading config file:", err)
+		return
 	}
-	fmt.Println("Tour controller started")
 
-	// Send an OSC message.
-	commandMap := map[string]string{
-		"command":   "play",
-		"file_name": "test.mp3",
-	}
+	// Create device
+	tourController := device.NewDevice(
+		config,
+		listener.OSC,
+		sender.OSC,
+		handleMessage,
+	)
 
-	commandJson, err := json.Marshal(commandMap)
+	// Start the device
+	err = tourController.Start()
 	if err != nil {
-		fmt.Println("Error marshalling command to JSON:", err)
+		fmt.Println("Error starting device:", err)
 		return
 	}
 
-	// for _, d := range tourController.devices {
-	// d.sender.Publish(
-	tourController.device.Send(
-		"/test",
-		[]byte(string(commandJson)),
-	)
-	// }
+	// Sleep for 2 seconds to allow the device to start.
+	time.Sleep(2 * time.Second)
 
 	// Keep the tour controller running.
 	for {
-		time.Sleep(1 * time.Millisecond)
+
+		fmt.Println("Main Loop")
+
+		// for len(devices) == 0 {
+		// 	fmt.Println("No devices registered yet")
+		// 	time.Sleep(1 * time.Second)
+		// }
+
+		// Show the menu.
+		// input := showMenu()
+
+		// for input != "0" {
+
+		// 	// Get message
+		// 	jsonData := getMessage(input)
+		// 	if jsonData == nil {
+		// 		continue
+		// 	}
+
+		// 	// Send command
+		// 	jsonDataBytes, err := json.Marshal(jsonData)
+		// 	if err != nil {
+		// 		fmt.Println("Error marshalling JSON data:", err)
+		// 		return
+		// 	}
+
+		// 	err = broadcast("/command/audio/", []byte(jsonDataBytes))
+		// 	if err != nil {
+		// 		fmt.Println("Error sending command:", err)
+		// 	}
+
+		// 	// Send heartbeat
+
+		// 	// Get the user input.
+		// 	input = showMenu()
+		// }
+
+		// Send heartbeat
+		err = tourController.SendHeartbeat()
+		if err != nil {
+			fmt.Println("Error sending heartbeat:", err)
+			return
+		}
+
+		time.Sleep(1 * time.Second)
+	}
+}
+
+func showMenu() string {
+	// Print a menu with the available commands.
+	fmt.Println("Available commands:")
+	fmt.Println("1. Play audio")
+	fmt.Println("2. Stop audio")
+	fmt.Println("3. Resume audio")
+	fmt.Println("4. Pause audio")
+	fmt.Println("0. Exit")
+
+	// Get the user input.
+	var input string
+	fmt.Print("Enter command: ")
+	fmt.Scanln(&input)
+
+	return input
+}
+
+func getMessage(input string) map[string]string {
+	jsonData := map[string]string{}
+
+	switch input {
+	case "1":
+		// Send the play command.
+		jsonData["action"] = "play"
+		jsonData["file"] = "audio.mp3"
+
+	case "2":
+		// Send the stop command.
+		jsonData["action"] = "stop"
+
+	case "3":
+		// Send the resume command.
+		jsonData["action"] = "resume"
+
+	case "4":
+		// Send the pause command.
+		jsonData["action"] = "pause"
+
+	default:
+		fmt.Println("Unknown command:", input)
+		return nil
+	}
+
+	return jsonData
+}
+
+func broadcast(address string, args_json []byte) error {
+	for _, d := range devices {
+		err := d.sender.Publish(address, args_json)
+		if err != nil {
+			return fmt.Errorf("error sending message to device %s: %v", d.deviceID, err)
+		}
 	}
+	return nil
 }