inital commit of go version
This commit is contained in:
parent
194eee7fe1
commit
bcdac1a8cb
|
@ -0,0 +1,100 @@
|
|||
# Created by https://www.gitignore.io/api/go,linux,intellij+all
|
||||
|
||||
### Go ###
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
### Intellij+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Ruby plugin and RubyMine
|
||||
/.rakeTasks
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
### Intellij+all Patch ###
|
||||
# Ignores the whole idea folder
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
|
||||
.idea/
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
# End of https://www.gitignore.io/api/go,linux,intellij+all
|
||||
|
||||
|
||||
bin/
|
||||
pkg/
|
||||
src/
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "proto"]
|
||||
path = proto
|
||||
url = git@github.com:LED-Freaks/LedD-protobuf.git
|
|
@ -0,0 +1,483 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"gopkg.in/mgo.v2"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"fmt"
|
||||
"gen/ledd"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
"github.com/op/go-logging"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// CONSTANTS
|
||||
|
||||
const VERSION = "0.1"
|
||||
const LOG_BACKEND = "BH"
|
||||
const LOG_CLIENTS = "CH"
|
||||
|
||||
// STRUCTS
|
||||
|
||||
type Config struct {
|
||||
Name string
|
||||
Daemon struct {
|
||||
Frontend struct {
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
Backend struct {
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
}
|
||||
Mongodb struct {
|
||||
Host string
|
||||
Port int
|
||||
Database string
|
||||
}
|
||||
}
|
||||
|
||||
type BackendManager struct {
|
||||
backends map[string]*Backend
|
||||
broadcast chan []byte
|
||||
register chan *Backend
|
||||
unregister chan *Backend
|
||||
}
|
||||
|
||||
type Backend struct {
|
||||
name string
|
||||
platformType string
|
||||
version string
|
||||
channel int32
|
||||
resolution int32
|
||||
socket net.Conn
|
||||
data chan []byte
|
||||
}
|
||||
|
||||
type ClientManager struct {
|
||||
clients map[*Client]bool
|
||||
broadcast chan []byte
|
||||
register chan *Client
|
||||
unregister chan *Client
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
platform string
|
||||
socket net.Conn
|
||||
data chan []byte
|
||||
}
|
||||
|
||||
type LED struct {
|
||||
name string
|
||||
channel []int32
|
||||
backend string
|
||||
color chan colorful.Color
|
||||
}
|
||||
|
||||
type LEDManager struct {
|
||||
leds map[string]*LED
|
||||
broadcast chan colorful.Color
|
||||
add chan *LED
|
||||
remove chan *LED
|
||||
}
|
||||
|
||||
// GLOBAL VARS
|
||||
|
||||
var log = logging.MustGetLogger("LedD")
|
||||
var backManager = BackendManager{}
|
||||
var clientManager = ClientManager{}
|
||||
var ledManager = LEDManager{}
|
||||
var LEDCollection = &mgo.Collection{}
|
||||
var config = Config{}
|
||||
|
||||
// SOCKET SETUP
|
||||
|
||||
func setupSocket(host string, port int, logTag string, backend bool) (func(), error) {
|
||||
ln, err := net.Listen("tcp4", fmt.Sprintf("%s:%d", host, port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("[%s] Ready to handle connections.", logTag)
|
||||
|
||||
return func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Warningf("%s", err)
|
||||
}
|
||||
log.Infof("[%s] New connection from %s", logTag, conn.RemoteAddr())
|
||||
if backend {
|
||||
backend := &Backend{socket: conn, data: make(chan []byte)}
|
||||
go backManager.receive(backend)
|
||||
go backManager.send(backend)
|
||||
} else {
|
||||
client := &Client{socket: conn, data: make(chan []byte)}
|
||||
go clientManager.receive(client)
|
||||
go clientManager.send(client)
|
||||
}
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LED MANAGER
|
||||
|
||||
func (manager *LEDManager) start() {
|
||||
for {
|
||||
select {
|
||||
case led := <-manager.add:
|
||||
log.Debugf("[%s] Request to add led: %s", led.backend, led.name)
|
||||
manager.leds[led.name] = led
|
||||
go manager.color(led)
|
||||
err := LEDCollection.Insert(led)
|
||||
if err != nil {
|
||||
log.Warning("[%s] Error while adding LED to database: %s", LOG_BACKEND, err)
|
||||
}
|
||||
case led := <-manager.remove:
|
||||
if _, ok := manager.leds[led.name]; ok {
|
||||
log.Debugf("[%s] Request to remove led %s", led.backend, led.name)
|
||||
delete(manager.leds, led.name)
|
||||
}
|
||||
case color := <-manager.broadcast:
|
||||
for _, led := range manager.leds {
|
||||
select {
|
||||
case led.color <- color:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *LEDManager) color(led *LED) {
|
||||
for {
|
||||
select {
|
||||
case color := <-led.color:
|
||||
led.setColor(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BACKEND HANDLER
|
||||
|
||||
func (manager *BackendManager) start() {
|
||||
for {
|
||||
select {
|
||||
case backend := <-manager.register:
|
||||
manager.backends[backend.name] = backend
|
||||
log.Debugf("[%s] New backend: %s", LOG_BACKEND, backend.niceName())
|
||||
wrapperMsg := &ledd.BackendWrapperMessage{
|
||||
Msg: &ledd.BackendWrapperMessage_MLedd{
|
||||
MLedd: &ledd.LedD{
|
||||
Name: config.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(wrapperMsg)
|
||||
if err != nil {
|
||||
log.Warningf("[%s] Failed to encode protobuf: %s", backend.niceName(), err)
|
||||
}
|
||||
|
||||
backend.data <- data
|
||||
case backend := <-manager.unregister:
|
||||
if _, ok := manager.backends[backend.name]; ok {
|
||||
log.Debugf("[%s] Backend %s removed: connection terminated", LOG_BACKEND, backend.socket.RemoteAddr())
|
||||
close(backend.data)
|
||||
delete(manager.backends, backend.name)
|
||||
}
|
||||
case message := <-manager.broadcast:
|
||||
for _, backend := range manager.backends {
|
||||
select {
|
||||
case backend.data <- message:
|
||||
default:
|
||||
close(backend.data)
|
||||
delete(manager.backends, backend.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *BackendManager) stop() {
|
||||
for _, backend := range manager.backends {
|
||||
close(backend.data)
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *BackendManager) send(backend *Backend) {
|
||||
defer backend.socket.Close()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-backend.data:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
backend.socket.Write(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *BackendManager) receive(backend *Backend) {
|
||||
for {
|
||||
message := make([]byte, 4096)
|
||||
|
||||
length, err := backend.socket.Read(message)
|
||||
if err != nil {
|
||||
log.Warningf("[%s] Read failed: %s", backend.niceName(), err)
|
||||
manager.unregister <- backend
|
||||
backend.socket.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
msgLen := binary.BigEndian.Uint32(message[0:3])
|
||||
|
||||
log.Debugf("[%s] Read %d bytes, first protobuf is %d long", backend.niceName(), length, msgLen)
|
||||
|
||||
backendMsg := &ledd.BackendWrapperMessage{}
|
||||
err = proto.Unmarshal(message[4:msgLen], backendMsg)
|
||||
if err != nil {
|
||||
log.Warningf("[%s] Couldn't decode protobuf msg!", backend.niceName())
|
||||
continue
|
||||
}
|
||||
|
||||
switch msg := backendMsg.Msg.(type) {
|
||||
case *ledd.BackendWrapperMessage_MBackend:
|
||||
nBackend := msg.MBackend
|
||||
backend.name = nBackend.Name
|
||||
backend.channel = nBackend.Channel
|
||||
backend.resolution = nBackend.Resolution
|
||||
backend.platformType = nBackend.Type
|
||||
backend.version = nBackend.Version
|
||||
log.Infof("[%s] %s is now identified as %s", LOG_BACKEND, backend.socket.RemoteAddr(), backend.niceName())
|
||||
backManager.register <- backend
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CLIENT HANDLER
|
||||
|
||||
func (manager *ClientManager) start() {
|
||||
for {
|
||||
select {
|
||||
case client := <-manager.register:
|
||||
manager.clients[client] = true
|
||||
log.Debugf("[%s] New frontend (%s)", LOG_CLIENTS, client.socket.RemoteAddr(), client.platform)
|
||||
case client := <-manager.unregister:
|
||||
if _, ok := manager.clients[client]; ok {
|
||||
log.Debugf("[%s] Removed client (%s)", LOG_CLIENTS, client.socket.RemoteAddr(), client.platform)
|
||||
close(client.data)
|
||||
delete(manager.clients, client)
|
||||
}
|
||||
case message := <-manager.broadcast:
|
||||
for connection := range manager.clients {
|
||||
select {
|
||||
case connection.data <- message:
|
||||
default:
|
||||
close(connection.data)
|
||||
delete(manager.clients, connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *ClientManager) send(client *Client) {
|
||||
defer client.socket.Close()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-client.data:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
client.socket.Write(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *ClientManager) receive(client *Client) {
|
||||
for {
|
||||
message := make([]byte, 4096)
|
||||
length, err := client.socket.Read(message)
|
||||
if err != nil {
|
||||
log.Warningf("[%s] Read failed: %s", client.socket.RemoteAddr(), err)
|
||||
manager.unregister <- client
|
||||
client.socket.Close()
|
||||
break
|
||||
}
|
||||
if length > 0 {
|
||||
msgLen := binary.BigEndian.Uint32(message[0:3])
|
||||
|
||||
log.Debugf("[%s] Read %d bytes, first protobuf is %d long", client.socket.RemoteAddr(), length, msgLen)
|
||||
|
||||
clientMsg := &ledd.ClientWrapperMessage{}
|
||||
err = proto.Unmarshal(message[4:msgLen], clientMsg)
|
||||
if err != nil {
|
||||
log.Warningf("[%s] Couldn't decode protobuf msg!", client.socket.RemoteAddr())
|
||||
continue
|
||||
}
|
||||
|
||||
switch msg := clientMsg.Msg.(type) {
|
||||
case *ledd.ClientWrapperMessage_MClient:
|
||||
client.platform = msg.MClient.Type
|
||||
log.Infof("[%s] %s is now identified as client (%s)", LOG_CLIENTS, client.socket.RemoteAddr(), client.platform)
|
||||
clientManager.register <- client
|
||||
case *ledd.ClientWrapperMessage_MGetLed:
|
||||
allLED := make([]*ledd.LED, 0)
|
||||
|
||||
for _, led := range ledManager.leds {
|
||||
allLED = append(allLED, &ledd.LED{Name: led.name})
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(&ledd.ClientWrapperMessage{Leds: allLED})
|
||||
if err != nil {
|
||||
log.Errorf("[%s] Error encoding protobuf: %s", client.socket.RemoteAddr(), err)
|
||||
}
|
||||
|
||||
client.data <- data
|
||||
case *ledd.ClientWrapperMessage_MAddLed:
|
||||
backend, ok := backManager.backends[msg.MAddLed.Backend]
|
||||
if !ok {
|
||||
log.Warningf("[%s] Can't add LED for non-existing backend %s", client.socket.RemoteAddr(), msg.MAddLed.Backend)
|
||||
}
|
||||
|
||||
nLED := &LED{
|
||||
name: msg.MAddLed.Name,
|
||||
channel: msg.MAddLed.Channel,
|
||||
backend: backend.name,
|
||||
}
|
||||
|
||||
ledManager.add <- nLED
|
||||
case *ledd.ClientWrapperMessage_MSetLed:
|
||||
led, ok := ledManager.leds[msg.MSetLed.Name]
|
||||
if !ok {
|
||||
log.Warningf("[%s] Failed to set LED %s: LED not found", client.socket.RemoteAddr(), msg.MSetLed.Name)
|
||||
}
|
||||
|
||||
led.color <- colorful.Hcl(msg.MSetLed.Colour.Hue, msg.MSetLed.Colour.Chroma, msg.MSetLed.Colour.Light)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HELPER
|
||||
|
||||
func check(e error) {
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) niceName() string {
|
||||
if backend.name != "" {
|
||||
return backend.name
|
||||
} else {
|
||||
return backend.socket.RemoteAddr().String()
|
||||
}
|
||||
}
|
||||
|
||||
func (led *LED) setColor(color colorful.Color) {
|
||||
backend := backManager.backends[led.backend]
|
||||
|
||||
if len(led.channel) != 3 {
|
||||
log.Warningf("[%s] Currently only RGB LEDs are supported", led.name)
|
||||
return
|
||||
}
|
||||
|
||||
cMap := make(map[int32]int32)
|
||||
|
||||
cMap[led.channel[0]] = int32(color.R * float64(backend.resolution))
|
||||
cMap[led.channel[1]] = int32(color.G * float64(backend.resolution))
|
||||
cMap[led.channel[2]] = int32(color.B * float64(backend.resolution))
|
||||
|
||||
wrapperMsg := &ledd.BackendWrapperMessage{
|
||||
Msg: &ledd.BackendWrapperMessage_MSetChannel{
|
||||
MSetChannel: &ledd.BackendSetChannel{
|
||||
NewChannelValues: cMap}}}
|
||||
|
||||
data, err := proto.Marshal(wrapperMsg)
|
||||
if err != nil {
|
||||
log.Warningf("[%s] Failed to encode protobuf msg to %s: %s", led.name, backend.name, err)
|
||||
}
|
||||
|
||||
backend.data <- data
|
||||
}
|
||||
|
||||
// MAIN
|
||||
|
||||
func main() {
|
||||
killSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(killSignals, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
log.Info("LedD", VERSION)
|
||||
|
||||
content, err := ioutil.ReadFile("ledd.yaml")
|
||||
check(err)
|
||||
|
||||
err = yaml.Unmarshal(content, &config)
|
||||
check(err)
|
||||
|
||||
session, err := mgo.Dial(fmt.Sprintf("%s:%d", config.Mongodb.Host, config.Mongodb.Port))
|
||||
check(err)
|
||||
defer session.Close()
|
||||
|
||||
LEDCollection = session.DB(config.Mongodb.Database).C("led")
|
||||
|
||||
backManager = BackendManager{
|
||||
backends: make(map[string]*Backend),
|
||||
broadcast: make(chan []byte),
|
||||
register: make(chan *Backend),
|
||||
unregister: make(chan *Backend),
|
||||
}
|
||||
|
||||
go backManager.start()
|
||||
|
||||
clientManager = ClientManager{
|
||||
clients: make(map[*Client]bool),
|
||||
broadcast: make(chan []byte),
|
||||
register: make(chan *Client),
|
||||
unregister: make(chan *Client),
|
||||
}
|
||||
|
||||
go clientManager.start()
|
||||
|
||||
ledManager = LEDManager{
|
||||
leds: make(map[string]*LED),
|
||||
broadcast: make(chan colorful.Color),
|
||||
add: make(chan *LED),
|
||||
remove: make(chan *LED),
|
||||
}
|
||||
|
||||
go ledManager.start()
|
||||
|
||||
var dbLEDs = make([]LED, 0)
|
||||
err = LEDCollection.Find(nil).All(&dbLEDs)
|
||||
if err != nil {
|
||||
log.Notice("Failed to load LEDs from db. If there should be LEDs, check db connection")
|
||||
}
|
||||
|
||||
for _, l := range dbLEDs {
|
||||
ledManager.add <- &l
|
||||
}
|
||||
|
||||
backendThread, err := setupSocket(config.Daemon.Backend.Host, config.Daemon.Backend.Port, LOG_BACKEND, true)
|
||||
check(err)
|
||||
go backendThread()
|
||||
|
||||
frontendThread, err := setupSocket(config.Daemon.Frontend.Host, config.Daemon.Frontend.Port, LOG_CLIENTS, false)
|
||||
check(err)
|
||||
go frontendThread()
|
||||
|
||||
log.Infof("All connection handler ready.")
|
||||
|
||||
<-killSignals
|
||||
|
||||
backManager.stop()
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
name: TestDaemon
|
||||
daemon:
|
||||
frontend:
|
||||
host: ""
|
||||
port: 5635
|
||||
backend:
|
||||
host: ""
|
||||
port: 5640
|
||||
mongodb:
|
||||
host: "127.0.0.1"
|
||||
port: 27017
|
||||
database: "ledd"
|
|
@ -0,0 +1 @@
|
|||
Subproject commit fe9a6d440c8e4344527c1cd50b5a7f50db6dd8ac
|
Loading…
Reference in New Issue