Browse Source

inital commit of go version

master
Giovanni Harting 2 years ago
parent
commit
bcdac1a8cb
5 changed files with 599 additions and 0 deletions
  1. +100
    -0
      .gitignore
  2. +3
    -0
      .gitmodules
  3. +483
    -0
      ledd.go
  4. +12
    -0
      ledd.yaml
  5. +1
    -0
      proto

+ 100
- 0
.gitignore 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
- 0
.gitmodules View File

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

+ 483
- 0
ledd.go 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
- 0
ledd.yaml 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
- 0
proto

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

Loading…
Cancel
Save