Commit b28c422a authored by Ефремов Алексей Максимович's avatar Ефремов Алексей Максимович
Browse files

Merge branch 'fix/artnet_channel_range' into 'master'

fixed invalid channel range & setScene method for artnet devices

See merge request !14
1 merge request!14fixed invalid channel range & setScene method for artnet devices
Pipeline #150330 passed with stages
in 2 minutes and 11 seconds
Showing with 220 additions and 25 deletions
+220 -25
......@@ -94,7 +94,7 @@ func (d *artnetDevice) SetScene(ctx context.Context, command models.SetScene) er
}
d.SaveUniverseToCache(ctx)
d.WriteValueToChannel(models.SetChannel{})
d.WriteUniverseToDevice()
d.CreateSceneChangedSignal()
return nil
}
......@@ -146,8 +146,8 @@ func (d *artnetDevice) WriteValueToChannel(command models.SetChannel) error {
return err
}
if command.Channel < 1 || command.Channel >= 512 {
return fmt.Errorf("channel number should be beetwen 1 and 511, but got: %v", command.Channel)
if command.Channel < 0 || command.Channel >= 511 {
return fmt.Errorf("channel number should be beetwen 0 and 511, but got: %v", command.Channel)
}
d.dev.SendDMXToAddress(d.Universe, artnet.Address{Net: d.net, SubUni: d.subUni})
......
......@@ -8,6 +8,7 @@ import (
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
)
func (b *BaseDevice) ReadUnvierse(ctx context.Context) error {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
......@@ -49,13 +50,13 @@ func (b *BaseDevice) WriteUniverse(ctx context.Context) error {
return nil
}
func (b *BaseDevice) ReadScenes(ctx context.Context){
func (b *BaseDevice) ReadScenes(ctx context.Context) {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
encodedScenesMap := make(map[string]string)
for sceneAlias := range b.Scenes {
......@@ -69,14 +70,41 @@ func (b *BaseDevice) ReadScenes(ctx context.Context){
}
for sceneAlias, encodedScene := range encodedScenesMap {
err := b.DecodeScene(encodedScene, b.Scenes[sceneAlias])
decodedScene := Scene{Alias: sceneAlias, ChannelMap: make(map[int]Channel)}
err := b.DecodeScene(encodedScene, decodedScene)
if err != nil {
b.Scenes[sceneAlias] = Scene{}
b.Logger.Warn(fmt.Sprintf("decoding cached scene '%s' failed", sceneAlias), zap.Error(err), zap.Any("device", b.Alias))
continue
}
err = b.ValidateCachedScene(decodedScene, b.Scenes[sceneAlias])
if err != nil {
b.Logger.Warn(fmt.Sprintf("invalid cached scene '%s'", sceneAlias), zap.Error(err), zap.Any("device", b.Alias))
} else {
b.Scenes[sceneAlias] = decodedScene
}
}
}
func (b *BaseDevice) ValidateCachedScene(cachedScene Scene, configuredScene Scene) error {
if len(cachedScene.ChannelMap) != len(configuredScene.ChannelMap) {
return fmt.Errorf("unequal channelMap sizes")
}
for cachedKey, cachedChannel := range cachedScene.ChannelMap {
configuredChannel, ok := configuredScene.ChannelMap[cachedKey]
if !ok {
return fmt.Errorf("cached channelMap key '%d' was not found in configured scene", cachedKey)
}
if configuredChannel.UniverseChannelID != cachedChannel.UniverseChannelID {
return fmt.Errorf("cached UniverseChanneldID is not equal to configured UniverseChannelID ('%d' != '%d')",
cachedChannel.UniverseChannelID, configuredChannel.UniverseChannelID)
}
}
return nil
}
func (b *BaseDevice) WriteScenes(ctx context.Context) {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
......@@ -103,8 +131,8 @@ func (b *BaseDevice) DecodeUniverse(sequence string) error {
}
previousLastChannel := -1
for i := 0; i < size; i+=9 {
subsequence := sequence[i:i+9]
for i := 0; i < size; i += 9 {
subsequence := sequence[i : i+9]
initialChannel, err := strconv.Atoi(subsequence[0:3])
if err != nil {
return err
......@@ -139,7 +167,7 @@ func (b *BaseDevice) DecodeUniverse(sequence string) error {
for j := initialChannel; j <= lastChannel; j++ {
b.Universe[j] = byte(channelValue)
}
}
previousLastChannel = lastChannel
}
......@@ -152,8 +180,8 @@ func (b *BaseDevice) EncodeUniverse() string {
var currentChannelValue int = int(b.Universe[0])
var sequenceStart int = 0
for idx, channel := range b.Universe {
if currentChannelValue != int(channel){
result += fmt.Sprintf("%03d", sequenceStart) + fmt.Sprintf("%03d", idx - 1) + fmt.Sprintf("%03d", currentChannelValue)
if currentChannelValue != int(channel) {
result += fmt.Sprintf("%03d", sequenceStart) + fmt.Sprintf("%03d", idx-1) + fmt.Sprintf("%03d", currentChannelValue)
currentChannelValue = int(channel)
sequenceStart = idx
}
......@@ -175,24 +203,24 @@ func (b *BaseDevice) EncodeScene(scene Scene) string {
return result
}
func (b *BaseDevice) DecodeScene(sequence string, scene Scene) error {
func (b *BaseDevice) DecodeScene(sequence string, scene Scene) (error) {
size := len(sequence)
if size % 9 != 0 {
return fmt.Errorf("got invalid RLE sequence size")
}
for i := 0; i < size; i+=9 {
sceneChannelID, err := strconv.Atoi(sequence[i:i+3])
for i := 0; i < size; i += 9 {
sceneChannelID, err := strconv.Atoi(sequence[i : i+3])
if err != nil {
return err
}
UniverseChannelID, err := strconv.Atoi(sequence[i+3:i+6])
UniverseChannelID, err := strconv.Atoi(sequence[i+3 : i+6])
if err != nil {
return err
}
channelValue, err := strconv.Atoi(sequence[i+6:i+9])
channelValue, err := strconv.Atoi(sequence[i+6 : i+9])
if err != nil {
return err
}
......
......@@ -10,7 +10,6 @@ import (
"git.miem.hse.ru/hubman/dmx-executor/internal/device"
"git.miem.hse.ru/hubman/dmx-executor/internal/models"
DMX "github.com/akualab/dmx"
)
func NewDMXDevice(ctx context.Context, signals chan core.Signal, conf device.DMXConfig, logger *zap.Logger, checkManager core.CheckRegistry) (device.Device, error) {
......@@ -27,7 +26,7 @@ func NewDMXDevice(ctx context.Context, signals chan core.Signal, conf device.DMX
type dmxDevice struct {
device.BaseDevice
path string
dev *DMX.DMX
dev *DMX
}
func (d *dmxDevice) reconnect() {
......@@ -48,7 +47,7 @@ func (d *dmxDevice) reconnect() {
}
func (d *dmxDevice) connect() {
dev, err := DMX.NewDMXConnection(d.path)
dev, err := NewDMXConnection(d.path)
if err != nil {
connCheck := core.NewCheck(
fmt.Sprintf(device.DeviceDisconnectedCheckLabelFormat, d.Alias),
......@@ -94,7 +93,7 @@ func (d *dmxDevice) WriteUniverseToDevice() error {
d.Mutex.Lock()
defer d.Mutex.Unlock()
for i := 1; i < 511; i++ {
for i := 0; i < 512; i++ {
err := d.dev.SetChannel(i, d.Universe[i])
if err != nil {
return fmt.Errorf("setting value to channel error: %v", err)
......@@ -167,12 +166,9 @@ func (d *dmxDevice) WriteValueToChannel(command models.SetChannel) error {
return err
}
if command.Channel < 1 || command.Channel >= 512 {
return fmt.Errorf("channel number should be beetwen 1 and 511, but got: %v", command.Channel)
}
err = d.dev.SetChannel(command.Channel, byte(command.Value))
if err != nil {
return fmt.Errorf("setting value to channel error: %v", err)
return err
}
d.Mutex.Lock()
......
// Simple Go package to send DMX messages.
// Copyright (c) 2013 AKUALAB INC. All Rights Reserved.
// www.akualab.com - @akualab - info@akualab.com
//
// CREDITS:
// Ported from pySimpleDMX (https://github.com/c0z3n/pySimpleDMX)
// Written by Michael Dvorkin
//
// GNU General Public License v3. http://www.gnu.org/licenses/
package dmx
import (
"fmt"
"io"
"log"
serial "github.com/tarm/goserial"
)
const (
START_VAL = 0x7E
END_VAL = 0xE7
BAUD = 57600
TIMEOUT = 1
DEV = "/dev/ttyUSB0"
FRAME_SIZE = 512
FRAME_SIZE_LOW = byte(FRAME_SIZE & 0xFF)
FRAME_SIZE_HIGH = byte(FRAME_SIZE >> 8 & 0xFF)
)
var labels = map[string]byte{
"GET_WIDGET_PARAMETERS": 3, // unused
"SET_WIDGET_PARAMETERS": 4, // unused
"RX_DMX_PACKET": 5, // unused
"TX_DMX_PACKET": 6,
"TX_RDM_PACKET_REQUEST": 7, // unused
"RX_DMX_ON_CHANGE": 8, // unused
}
// A serial DMX connection.
type DMX struct {
dev string
frame [FRAME_SIZE]byte
packet [FRAME_SIZE + 10]byte
serial io.ReadWriteCloser
redChan int
blueChan int
greenChan int
brightnessChan int
}
// Creates a new DMX connection using a serial device.
func NewDMXConnection(device string) (dmx *DMX, err error) {
dmx = &DMX{}
// Set serial device or use default.
dmx.dev = device
if len(dmx.dev) == 0 {
dmx.dev = DEV
}
c := &serial.Config{Name: dmx.dev, Baud: BAUD}
dmx.serial, err = serial.OpenPort(c)
if err != nil {
return
}
log.Printf("Opened port [%s].", dmx.dev)
return
}
// Set channel level in the dmx frame to be rendered
// the next time Render() is called.
func (dmx *DMX) SetChannel(channel int, val byte) error {
err := checkChannelID(channel)
if err != nil {
return err
}
dmx.frame[channel] = val
return nil
}
// Turn off a specific channel.
func (dmx *DMX) ClearChannel(channel int) error {
err := checkChannelID(channel)
if err != nil {
return err
}
dmx.frame[channel] = 0
return nil
}
// Turn off all channels.
func (dmx *DMX) ClearAll() {
for i := range dmx.frame {
dmx.frame[i] = 0
}
}
// Send frame to serial device.
func (dmx *DMX) Render() error {
p := dmx.packet[:0]
p = append(p, START_VAL)
p = append(p, labels["TX_DMX_PACKET"])
p = append(p, FRAME_SIZE_LOW)
p = append(p, FRAME_SIZE_HIGH)
p = append(p, 0)
p = append(p, dmx.frame[0:]...)
p = append(p, END_VAL)
// Write dmx frame.
_, err := dmx.serial.Write(p)
if err != nil {
return err
}
return nil
}
// Close serial port.
func (dmx *DMX) Close() error {
return dmx.serial.Close()
}
// Convenience method to map colors and brightness to channels.
func (dmx *DMX) ChannelMap(brightness, red, green, blue int) {
checkChannelID(brightness)
checkChannelID(red)
checkChannelID(green)
checkChannelID(blue)
dmx.brightnessChan = brightness
dmx.redChan = red
dmx.greenChan = green
dmx.blueChan = blue
}
// Configures RGB+Brightness channels and renders the color.
// Call ChannelMap to configure the RGB channels before calling
// this method.
func (dmx *DMX) SendRGB(brightness, red, green, blue byte) (e error) {
dmx.ClearAll()
e = dmx.SetChannel(dmx.brightnessChan, brightness)
if e != nil {
return
}
e = dmx.SetChannel(dmx.redChan, red)
if e != nil {
return
}
e = dmx.SetChannel(dmx.greenChan, green)
if e != nil {
return
}
e = dmx.SetChannel(dmx.blueChan, blue)
if e != nil {
return
}
e = dmx.Render()
if e != nil {
return
}
return
}
func checkChannelID(id int) error {
if (id > 511) || (id < 0) {
return fmt.Errorf("invalid channel [%d]", id)
}
return nil
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment