inital commit of go version

This commit is contained in:
Giovanni Harting 2017-12-12 19:12:14 +01:00
parent 194eee7fe1
commit bcdac1a8cb
5 changed files with 599 additions and 0 deletions

100
.gitignore vendored Normal file
View File

@ -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/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "proto"]
path = proto
url = git@github.com:LED-Freaks/LedD-protobuf.git

483
ledd.go Normal file
View File

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

12
ledd.yaml Normal file
View File

@ -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"

1
proto Submodule

@ -0,0 +1 @@
Subproject commit fe9a6d440c8e4344527c1cd50b5a7f50db6dd8ac