diff --git a/src/board/board_dummy.go b/src/board/board_dummy.go index ef458dd..b93be4e 100644 --- a/src/board/board_dummy.go +++ b/src/board/board_dummy.go @@ -15,7 +15,7 @@ type DummyChannel struct { name string mqttStateTopic string mqttCommandTopic string - Value bool + value bool onboot string parent *DummyBoard onChannelUpdateFunctions map[string]onChannelUpdateFunction @@ -49,7 +49,7 @@ func newDummyChannel(v *viper.Viper, channelID string) DummyChannel { name: v.GetString("name"), mqttStateTopic: v.GetString("mqtt.statetopic"), mqttCommandTopic: v.GetString("mqtt.commandtopic"), - Value: value, + value: value, onboot: v.GetString("onboot"), } } @@ -97,17 +97,17 @@ func newDummyBoard(v *viper.Viper, id string) *Board { } func (c *DummyChannel) Toggle() (bool, error) { - c.Value = !c.Value + c.value = !c.value c.SaveLastState() for f := range c.onChannelUpdateFunctions { - c.onChannelUpdateFunctions[f](!c.Value, c) + c.onChannelUpdateFunctions[f](!c.value, c) } - return c.Value, nil + return c.value, nil } func (c *DummyChannel) On() error { - oldval := c.Value - c.Value = true + oldval := c.value + c.value = true c.SaveLastState() for f := range c.onChannelUpdateFunctions { c.onChannelUpdateFunctions[f](oldval, c) @@ -116,8 +116,8 @@ func (c *DummyChannel) On() error { } func (c *DummyChannel) Off() error { - oldval := c.Value - c.Value = true + oldval := c.value + c.value = true c.SaveLastState() for f := range c.onChannelUpdateFunctions { c.onChannelUpdateFunctions[f](oldval, c) @@ -126,26 +126,22 @@ func (c *DummyChannel) Off() error { } func (c *DummyChannel) ToString() string { - if !c.Value { + if !c.value { return "off" } return "on" } -// func (c *DummyChannel) UpdateMQTT() { -// mqtt.Publish(c.MQTTStateTopic, c.ToString()) // bisogna rimuovere questa -// } - func (c *DummyChannel) SaveLastState() { if c.onboot == "last" { s := fmt.Sprintf("boards.%s.channels.%s.lastvalue", c.parent.ID, c.ID) - viper.Set(s, c.Value) + viper.Set(s, c.value) viper.WriteConfig() } } func (c *DummyChannel) Status() bool { - return c.Value + return c.value } func (c *DummyChannel) Parent() Board { @@ -163,7 +159,7 @@ func (b DummyBoard) Dump() { } } -func (b DummyBoard) Initialize() { +func (b *DummyBoard) Initialize() { return } @@ -200,13 +196,13 @@ func (c *DummyChannel) SetMQTTCommandTopic(str string) { func (c *DummyChannel) MQTTHandler(client PahoMQTT.Client, msg PahoMQTT.Message) { switch string(msg.Payload()) { case "on": - if !c.Value { - c.Value = true + if !c.value { + c.value = true c.SaveLastState() } case "off": - if c.Value { - c.Value = false + if c.value { + c.value = false c.SaveLastState() } } diff --git a/src/board/board_i2c.go b/src/board/board_i2c.go new file mode 100644 index 0000000..bfca622 --- /dev/null +++ b/src/board/board_i2c.go @@ -0,0 +1,406 @@ +package board + +import ( + "fmt" + "log" + "strings" + "time" + + "git.openpdu.org/OpenPDU/openpdu/i2c" + "git.openpdu.org/OpenPDU/openpdu/syslog" + PahoMQTT "github.com/eclipse/paho.mqtt.golang" + "github.com/spf13/viper" + I2C "periph.io/x/periph/conn/i2c" +) + +type I2CChannel struct { + ID string + Num uint + name string + mqttStateTopic string + mqttCommandTopic string + onboot string + parent *I2CBoard + onChannelUpdateFunctions map[string]onChannelUpdateFunction +} + +type I2CBoard struct { + ID string + Name string + ChannelCount uint + channels []*I2CChannel + i2cDevice *I2C.Dev + inverted bool + i2cDataA []byte + i2cDataB []byte + lastUpdate time.Time +} + +func newI2CChannel(v *viper.Viper, channelID string) I2CChannel { + v.SetDefault("name", "unknown") + v.SetDefault("lastValue", false) + v.SetDefault("onboot", "off") + v.SetDefault("mqtt.statetopic", v.GetString("name")) + + return I2CChannel{ + ID: channelID, + Num: v.GetUint("num"), + name: v.GetString("name"), + mqttStateTopic: v.GetString("mqtt.statetopic"), + mqttCommandTopic: v.GetString("mqtt.commandtopic"), + onboot: v.GetString("onboot"), + } +} + +func newI2CBoard(v *viper.Viper, id string) *Board { + var b *I2CBoard + + v.SetDefault("name", "board "+id) + v.SetDefault("type", "i2c") + v.SetDefault("channelCount", 0) + v.SetDefault("channels", "") + v.SetDefault("inverted", true) + + if v.GetInt("channelCount") > 0 { + for i := 0; i < v.GetInt("channelCount"); i++ { + v.SetDefault("channels."+fmt.Sprint(i)+".num", i) + } + } + + b = &I2CBoard{ + ID: id, + Name: v.GetString("name"), + ChannelCount: v.GetUint("channelCount"), + inverted: v.GetBool("inverted"), + } + + channels := make([]*I2CChannel, v.GetInt("channelCount")) + if v.GetInt("channelCount") > 0 { + channelsConfig := v.Sub("channels") + if channelsConfig != nil { + for channelid1 := range channelsConfig.AllSettings() { + channelid := strings.ToLower(channelid1) + channelConfig := channelsConfig.Sub(channelid) + c := newI2CChannel(channelConfig, channelid) + c.parent = b + if c.Num >= v.GetUint("channelCount") { + continue + } + channels[c.Num] = &c + AllChannels[c.ID] = &c + } + } + } + b.channels = channels + syslog.Info("Created new I2C board") + var b1 Board = b + return &b1 +} + +func (c *I2CChannel) Toggle() (bool, error) { + oldval := c.Status() + c.setToggle() + c.parent.WriteStatus() + c.SaveLastState() + for f := range c.onChannelUpdateFunctions { + c.onChannelUpdateFunctions[f](oldval, c) + } + return c.Status(), nil +} + +func (c *I2CChannel) setToggle() { + var data *[]byte + var mask byte + + if c.Num < 8 { + data = &c.parent.i2cDataA + } else { + data = &c.parent.i2cDataB + } + mask = 1 << (c.Num % 8) + log.Printf("toggle1: %v \n", c.parent.i2cDataA[0]) + (*data)[0] ^= mask + log.Printf("toggle2: %v \n", c.parent.i2cDataA[0]) +} + +func (c *I2CChannel) On() error { + oldval := c.Status() + c.SetOn() + c.parent.WriteStatus() + c.SaveLastState() + for f := range c.onChannelUpdateFunctions { + c.onChannelUpdateFunctions[f](oldval, c) + } + return nil +} + +func (c *I2CChannel) SetOn() { + var data *[]byte + var mask byte + + if c.Num < 8 { + data = &c.parent.i2cDataA + } else { + data = &c.parent.i2cDataB + } + mask = 1 << (c.Num % 8) + (*data)[0] |= mask +} + +func (c *I2CChannel) Off() error { + oldval := c.Status() + c.SetOff() + c.parent.WriteStatus() + c.SaveLastState() + for f := range c.onChannelUpdateFunctions { + c.onChannelUpdateFunctions[f](oldval, c) + } + return nil +} + +func (c *I2CChannel) SetOff() { + var data *[]byte + var mask byte + + if c.Num < 8 { + data = &c.parent.i2cDataA + } else { + data = &c.parent.i2cDataB + } + mask = (1 << (c.Num % 8)) ^ 0xFF + (*data)[0] &= mask +} + +func (c *I2CChannel) ToString() string { + if !c.Status() { + return "off" + } + return "on" +} + +func (c *I2CChannel) SaveLastState() { + if c.onboot == "last" { + s := fmt.Sprintf("boards.%s.channels.%s.lastvalue", c.parent.ID, c.ID) + viper.Set(s, c.Status()) + viper.WriteConfig() + } +} + +func (c *I2CChannel) Status() bool { + var data *[]byte + c.parent.ReadStatus() + if c.Num < 8 { + data = &c.parent.i2cDataA + } else { + data = &c.parent.i2cDataB + } + value := ((*data)[0] >> (c.Num % 8) & 1) == 1 + log.Printf("status ch %v: %v", c.Num, value) + return value +} + +func (c *I2CChannel) Parent() Board { + return c.parent +} + +func (c *I2CChannel) Dump() { + log.Printf(" Channel %d (on boot: %s): %s \n", c.Num, c.onboot, c.name) +} + +func (b I2CBoard) Dump() { + log.Printf("Board '%s' (id: %s): %d channels\n", b.Name, b.ID, b.ChannelCount) + for c := range b.channels { + b.channels[c].Dump() + } +} + +func (b *I2CBoard) ReadStatus() { + now := time.Now() + diff := now.Sub(b.lastUpdate) + ns := diff.Nanoseconds() + if ns < 10*1000000 { // 10ms + // syslog.Debug(fmt.Sprintf("I2C update skipped because less than 10ms passed (%v)", ns)) + return + } + + replyA := [1]byte{} + replyB := [1]byte{} + b.i2cDevice.Tx([]byte{0x12}, replyA[:]) + b.i2cDevice.Tx([]byte{0x13}, replyB[:]) + + log.Printf("read1: %v \n", replyA) + if b.inverted { + replyA[0] ^= 0xFF + replyB[0] ^= 0xFF + } + log.Printf("read2: %v \n", replyA) + + b.i2cDataA[0] = replyA[0] + b.i2cDataB[0] = replyB[0] + b.lastUpdate = time.Now() +} + +func (b *I2CBoard) WriteStatus() { + // var dA, dB, oA, oB []byte + var dA, dB []byte + + dA = []byte{0x12, 0x00} + dB = []byte{0x13, 0x00} + + dA[1] = b.i2cDataA[0] + + log.Printf("write1: %v \n", dA) + + dB[1] = b.i2cDataB[0] + if b.inverted { + dA[1] ^= 0xff + dB[1] ^= 0xff + } + // dA = append([]byte{0x12}, dA...) + log.Printf("write2: %v \n", dA) + // dB = append([]byte{0x13}, dB...) + + // b.i2cDevice.Tx(dA, oA) + // b.i2cDevice.Tx(dB, oB) + + if _, err := b.i2cDevice.Write(dA); err != nil { + syslog.Err(err.Error()) + } + if _, err := b.i2cDevice.Write(dB); err != nil { + syslog.Err(err.Error()) + } + + log.Printf("write3: %v \n", dA) + // log.Printf("write3: %v - %v\n", dA, oA) + + b.lastUpdate = time.Now() +} + +func (b *I2CBoard) Initialize() { + syslog.Info("Initializing I2C board") + + for { + if i2c.I2Cbus == nil { + syslog.Warning("i2cboard: i2cbus not found") + time.Sleep(1 * time.Second) + continue + } else { + break + } + } + + /* + * MCP23017 - can work in 8bit mode or 16bit mode, you have to set IOCON.BANK=1 (8bit) or =0 (16bit default) + * 16 bit mode: + * 0x00 IO direction bank A (1=input 0=output) + * 0x01 IO direction bank B (1=input 0=output) + * 0x12 GPIO A + * 0x13 GPIO B + * + * 8 bit mode + * 0x00 IO direction (1=input 0=output) + * 0x09 GPIO + */ + + mydevice := &I2C.Dev{Addr: 0x27, Bus: i2c.I2Cbus} + + // set all output + write := []byte{0x0, 0x0} + if _, err := mydevice.Write(write); err != nil { + syslog.Err(err.Error()) + } + write = []byte{0x01, 0x0} + if _, err := mydevice.Write(write); err != nil { + syslog.Err(err.Error()) + } + + b.i2cDevice = mydevice + b.i2cDataA = make([]byte, 1) + b.i2cDataB = make([]byte, 1) + syslog.Info("I2C board: reading initial status") + b.ReadStatus() + + for i := range b.channels { + c := b.channels[i] + switch c.onboot { + case "on": + c.SetOn() + case "off": + c.SetOff() + case "last": + s := fmt.Sprintf("boards.%s.channels.%s.lastvalue", c.parent.ID, c.ID) + + switch viper.GetBool(s) { + case true: + c.SetOn() + case false: + c.SetOff() + } + } + syslog.Info(fmt.Sprintf("Channel %d status: %v", i, c.Status())) + } + + b.WriteStatus() +} + +func (b *I2CBoard) Channel(num uint64) Channel { + return b.channels[num] +} + +func (c *I2CChannel) Name() string { + return c.name +} + +func (c *I2CChannel) OnBoot() string { + return c.onboot +} + +func (c *I2CChannel) SetOnBoot(str string) { + c.onboot = str + s := fmt.Sprintf("boards.%s.channels.%s.onboot", c.parent.ID, c.ID) + viper.Set(s, str) +} + +func (c *I2CChannel) SetMQTTStateTopic(str string) { + c.mqttStateTopic = str + s := fmt.Sprintf("boards.%s.channels.%s.mqtt.statetopic", c.parent.ID, c.ID) + viper.Set(s, str) +} + +func (c *I2CChannel) SetMQTTCommandTopic(str string) { + c.mqttCommandTopic = str + s := fmt.Sprintf("boards.%s.channels.%s.mqtt.commandtopic", c.parent.ID, c.ID) + viper.Set(s, str) +} + +func (c *I2CChannel) MQTTHandler(client PahoMQTT.Client, msg PahoMQTT.Message) { + switch string(msg.Payload()) { + case "on": + if !c.Status() { + c.On() + } + case "off": + if c.Status() { + c.Off() + } + } +} + +func (c *I2CChannel) MQTTStateTopic() string { + return c.mqttStateTopic +} + +func (c *I2CChannel) MQTTCommandTopic() string { + return c.mqttCommandTopic +} + +func init() { + RegisterBoardType("i2c", newI2CBoard) +} + +func (c *I2CChannel) AddOnChannelUpdateFunction(name string, f onChannelUpdateFunction) { + if c.onChannelUpdateFunctions == nil { + c.onChannelUpdateFunctions = make(map[string]onChannelUpdateFunction) + } + c.onChannelUpdateFunctions[name] = f +} diff --git a/src/board/board_mqtt.go b/src/board/board_mqtt.go index 938ce3e..0eb14e5 100644 --- a/src/board/board_mqtt.go +++ b/src/board/board_mqtt.go @@ -17,7 +17,7 @@ type MQTTChannel struct { name string mqttStateTopic string mqttCommandTopic string - Value bool + value bool onboot string parent *MQTTBoard MQTTRemoteStateTopic string @@ -66,7 +66,7 @@ func newMQTTChannel(v *viper.Viper, channelID string) MQTTChannel { name: v.GetString("name"), mqttStateTopic: v.GetString("mqtt.statetopic"), mqttCommandTopic: v.GetString("mqtt.commandtopic"), - Value: value, + value: value, onboot: v.GetString("onboot"), MQTTRemoteStateTopic: v.GetString("mqttremote.statetopic"), MQTTRemoteCommandTopic: v.GetString("mqttremote.commandtopic"), @@ -133,18 +133,18 @@ func init() { } func (c *MQTTChannel) Toggle() (bool, error) { - c.Value = !c.Value + c.value = !c.value c.SaveLastState() for f := range c.onChannelUpdateFunctions { - c.onChannelUpdateFunctions[f](!c.Value, c) + c.onChannelUpdateFunctions[f](!c.value, c) } c.UpdateRemoteMQTT() - return c.Value, nil + return c.value, nil } func (c *MQTTChannel) On() error { - oldval := c.Value - c.Value = true + oldval := c.value + c.value = true c.SaveLastState() for f := range c.onChannelUpdateFunctions { c.onChannelUpdateFunctions[f](oldval, c) @@ -154,8 +154,8 @@ func (c *MQTTChannel) On() error { } func (c *MQTTChannel) Off() error { - oldval := c.Value - c.Value = false + oldval := c.value + c.value = false c.SaveLastState() for f := range c.onChannelUpdateFunctions { c.onChannelUpdateFunctions[f](oldval, c) @@ -165,7 +165,7 @@ func (c *MQTTChannel) Off() error { } func (c *MQTTChannel) ToString() string { - if !c.Value { + if !c.value { return "off" } return "on" @@ -174,7 +174,7 @@ func (c *MQTTChannel) ToString() string { func (c *MQTTChannel) SaveLastState() { if c.onboot == "last" { s := fmt.Sprintf("boards.%s.channels.%s.lastvalue", c.parent.ID, c.ID) - viper.Set(s, c.Value) + viper.Set(s, c.value) viper.WriteConfig() } } @@ -183,7 +183,7 @@ func (c *MQTTChannel) UpdateRemoteMQTT() { connected := c.parent.MQTTClient.IsConnected() if connected { v := c.MQTTRemotePayloadOff - if c.Value { + if c.value { v = c.MQTTRemotePayloadOn } c.parent.MQTTClient.Publish(c.MQTTRemoteCommandTopic, 0, false, v) @@ -191,7 +191,7 @@ func (c *MQTTChannel) UpdateRemoteMQTT() { } func (c *MQTTChannel) Status() bool { - return c.Value + return c.value } func (c *MQTTChannel) Parent() Board { @@ -261,16 +261,16 @@ func (b *MQTTBoard) Initialize() { func (c *MQTTChannel) MQTTRemoteHandler(client PahoMQTT.Client, msg PahoMQTT.Message) { switch string(msg.Payload()) { case c.MQTTRemotePayloadOn: - if !c.Value { - c.Value = true + if !c.value { + c.value = true c.SaveLastState() for f := range c.onChannelUpdateFunctions { c.onChannelUpdateFunctions[f](false, c) } } case c.MQTTRemotePayloadOff: - if c.Value { - c.Value = false + if c.value { + c.value = false c.SaveLastState() for f := range c.onChannelUpdateFunctions { c.onChannelUpdateFunctions[f](true, c) @@ -287,14 +287,14 @@ func (c *MQTTChannel) MQTTHandler(client PahoMQTT.Client, msg PahoMQTT.Message) )) switch string(msg.Payload()) { case "on": - if !c.Value { - c.Value = true + if !c.value { + c.value = true c.SaveLastState() c.UpdateRemoteMQTT() } case "off": - if c.Value { - c.Value = false + if c.value { + c.value = false c.SaveLastState() c.UpdateRemoteMQTT() }