openpdu/src/board/board_i2c.go

392 lines
8.1 KiB
Go

package board
import (
"fmt"
"log"
"strings"
"time"
"git.openpdu.org/OpenPDU/openpdu/config"
"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/conn/v3/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)
(*data)[0] ^= mask
}
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)
config.Set(s, c.Status())
config.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
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[:])
if b.inverted {
replyA[0] ^= 0xFF
replyB[0] ^= 0xFF
}
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]
dB[1] = b.i2cDataB[0]
if b.inverted {
dA[1] ^= 0xff
dB[1] ^= 0xff
}
if _, err := b.i2cDevice.Write(dA); err != nil {
syslog.Err(err.Error())
}
if _, err := b.i2cDevice.Write(dB); err != nil {
syslog.Err(err.Error())
}
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 config.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)
config.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)
config.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)
config.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
}