commit
268e784cb5
@ -0,0 +1,4 @@ |
||||
/cli-mon |
||||
/.idea/cli-mon.iml |
||||
/.idea/modules.xml |
||||
/.idea/vcs.xml |
@ -0,0 +1,8 @@ |
||||
# Default ignored files |
||||
/shelf/ |
||||
/workspace.xml |
||||
# Editor-based HTTP Client requests |
||||
/httpRequests/ |
||||
# Datasource local storage ignored files |
||||
/dataSources/ |
||||
/dataSources.local.xml |
@ -0,0 +1,141 @@ |
||||
package can |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"errors" |
||||
"io" |
||||
"net" |
||||
"time" |
||||
"unsafe" |
||||
|
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
const CAN_ID_071 = 0x071 |
||||
const CAN_ID_073 = 0x073 |
||||
const CAN_ID_021 = 0x021 |
||||
const CAN_ID_022 = 0x022 |
||||
|
||||
type CanFrame struct { |
||||
CanId uint32 |
||||
Payload []uint8 |
||||
Date string |
||||
} |
||||
|
||||
// NewCan New returns a new CAN bus socket.
|
||||
func NewCan() (*Socket, error) { |
||||
fd, err := unix.Socket(unix.AF_CAN, unix.SOCK_RAW, unix.CAN_RAW) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &Socket{dev: device{fd}}, nil |
||||
} |
||||
|
||||
// Socket is a high-level representation of a CANBus socket.
|
||||
type Socket struct { |
||||
iface *net.Interface |
||||
addr *unix.SockaddrCAN |
||||
dev device |
||||
} |
||||
|
||||
// Close closes the CAN bus socket.
|
||||
func (sck *Socket) Close() error { |
||||
return unix.Close(sck.dev.fd) |
||||
} |
||||
|
||||
func (sck *Socket) Bind(addr string, filter []unix.CanFilter) error { |
||||
iface, err := net.InterfaceByName(addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
sck.iface = iface |
||||
sck.addr = &unix.SockaddrCAN{Ifindex: sck.iface.Index} |
||||
|
||||
//add filter
|
||||
if filter != nil { |
||||
unix.SetsockoptCanRawFilter(sck.dev.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FILTER, filter) |
||||
} |
||||
|
||||
return unix.Bind(sck.dev.fd, sck.addr) |
||||
} |
||||
|
||||
// Recv receives data from the CAN socket.
|
||||
// id is the CAN_frame id the data was originated from.
|
||||
func (sck *Socket) Recv() (id uint32, data []byte, err error) { |
||||
var buf [frameSize]byte |
||||
n, err := io.ReadFull(sck.dev, buf[:]) |
||||
if err != nil { |
||||
return id, data, err |
||||
} |
||||
|
||||
if n != len(buf) { |
||||
return id, data, io.ErrUnexpectedEOF |
||||
} |
||||
|
||||
id = binary.LittleEndian.Uint32(buf[:4]) |
||||
//TODO make correct switch betwin EFF and SFF
|
||||
id &= unix.CAN_EFF_MASK |
||||
data = make([]byte, buf[4]) |
||||
copy(data, buf[8:]) |
||||
return id, data, nil |
||||
} |
||||
|
||||
type device struct { |
||||
fd int |
||||
} |
||||
|
||||
func (d device) Read(data []byte) (int, error) { |
||||
return unix.Read(d.fd, data) |
||||
} |
||||
|
||||
func (d device) Write(data []byte) (int, error) { |
||||
return unix.Write(d.fd, data) |
||||
} |
||||
|
||||
const frameSize = unsafe.Sizeof(canframe{}) |
||||
|
||||
// frame is a can_frame.
|
||||
type canframe struct { |
||||
ID uint32 |
||||
Len byte |
||||
_ [3]byte |
||||
Data [8]byte |
||||
} |
||||
|
||||
func StartCan(canbus string) (<-chan *CanFrame, error) { |
||||
|
||||
var filter []unix.CanFilter = []unix.CanFilter{ |
||||
{Id: CAN_ID_071, Mask: 0xFFF}, |
||||
{Id: CAN_ID_073, Mask: 0xFFF}, |
||||
{Id: CAN_ID_021, Mask: 0xFFF}, |
||||
{Id: CAN_ID_022, Mask: 0xFFF}, |
||||
} |
||||
|
||||
if socket, err := NewCan(); err == nil { |
||||
|
||||
if cerr := socket.Bind(canbus, filter); cerr == nil { |
||||
|
||||
canevents := make(chan *CanFrame) |
||||
|
||||
go func() { |
||||
defer close(canevents) |
||||
defer socket.Close() |
||||
|
||||
for { //TODO implement stop
|
||||
if id, data, serr := socket.Recv(); serr == nil { |
||||
canevents <- &CanFrame{ |
||||
Date: time.Now().String(), |
||||
CanId: id, |
||||
Payload: data, |
||||
} |
||||
} |
||||
} |
||||
}() |
||||
|
||||
return canevents, nil |
||||
} |
||||
} |
||||
return nil, errors.New("can't bind can socket") |
||||
} |
@ -0,0 +1,96 @@ |
||||
package can |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"os" |
||||
"regexp" |
||||
"strconv" |
||||
) |
||||
|
||||
// (2022-07-08 16:54:15.587099) can0 100 [8] 00 00 00 00 00 00 64 00
|
||||
// (1670578900.771868) can0 00004021#00000000FCFFFFFF
|
||||
const EXPR_HR = "\\((.*)\\)\\s+can\\d+\\s+(\\S+)\\s+\\[\\d\\]\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)\\s+([A-F0-9]+)" |
||||
const EXPR_M = "\\((.*)\\)\\s+can\\d+\\s+(\\S+)#([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})" |
||||
const length = 11 |
||||
|
||||
var patternHr = regexp.MustCompile(EXPR_HR) |
||||
var patternM = regexp.MustCompile(EXPR_M) |
||||
|
||||
func Readfile(filename *string) <-chan *CanFrame { |
||||
c := make(chan *CanFrame) |
||||
|
||||
go func() { |
||||
defer close(c) |
||||
|
||||
file, err := os.Open(*filename) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
os.Exit(1) |
||||
} |
||||
defer file.Close() |
||||
|
||||
scanner := bufio.NewScanner(file) |
||||
process(scanner, c) |
||||
}() |
||||
|
||||
return c |
||||
} |
||||
|
||||
func process(scanner *bufio.Scanner, ch chan<- *CanFrame) { |
||||
for scanner.Scan() { |
||||
text := scanner.Text() |
||||
if frame := fromString(&text); frame != nil { |
||||
ch <- frame |
||||
} |
||||
} |
||||
|
||||
if err := scanner.Err(); err != nil { |
||||
fmt.Println(err) |
||||
} |
||||
} |
||||
|
||||
func ReadStdin() <-chan *CanFrame { |
||||
c := make(chan *CanFrame) |
||||
|
||||
go func() { |
||||
scanner := bufio.NewScanner(os.Stdin) |
||||
process(scanner, c) |
||||
}() |
||||
return c |
||||
} |
||||
|
||||
func fromString(text *string) *CanFrame { |
||||
var submatch []string |
||||
if submatch = patternHr.FindStringSubmatch(*text); submatch == nil { |
||||
if submatch = patternM.FindStringSubmatch(*text); submatch == nil { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
if len(submatch) == length { |
||||
canid, err := strconv.ParseUint(submatch[2], 16, 0) |
||||
if err != nil { |
||||
return nil |
||||
} |
||||
|
||||
payload := make([]uint8, 8) |
||||
for idx, octet := range submatch[3:] { |
||||
val, err := strconv.ParseUint(octet, 16, 0) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
continue |
||||
} |
||||
|
||||
payload[idx] = uint8(val) |
||||
} |
||||
|
||||
return &CanFrame{ |
||||
Date: submatch[1], |
||||
CanId: uint32(canid), |
||||
Payload: payload, |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,44 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"cli-mon/can" |
||||
"cli-mon/ui" |
||||
"cli-mon/yabl" |
||||
"flag" |
||||
"fmt" |
||||
"os" |
||||
) |
||||
|
||||
func main() { |
||||
filename := flag.String("f", "", "Candump filename") |
||||
canbus := flag.String("i", "", "CAN bus interface") |
||||
stdin := flag.Bool("s", false, "Read from stdin") |
||||
flag.Parse() |
||||
|
||||
var frames <-chan *can.CanFrame |
||||
|
||||
switch { |
||||
case *stdin: |
||||
frames = can.ReadStdin() |
||||
case len(*filename) > 0: |
||||
frames = can.Readfile(filename) |
||||
case len(*canbus) > 0: |
||||
var err error |
||||
frames, err = can.StartCan(*canbus) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
default: |
||||
flag.Usage() |
||||
} |
||||
|
||||
if frames == nil { |
||||
fmt.Println("Get no data") |
||||
os.Exit(0) |
||||
} |
||||
|
||||
var messages = yabl.StartProtocolParsing(frames) |
||||
|
||||
ui.InitCliApp(messages) |
||||
} |
@ -0,0 +1,14 @@ |
||||
module cli-mon |
||||
|
||||
go 1.19 |
||||
|
||||
require ( |
||||
github.com/gizak/termui/v3 v3.1.0 |
||||
golang.org/x/sys v0.3.0 |
||||
) |
||||
|
||||
require ( |
||||
github.com/mattn/go-runewidth v0.0.2 // indirect |
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect |
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect |
||||
) |
@ -0,0 +1,10 @@ |
||||
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc= |
||||
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY= |
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= |
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= |
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= |
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= |
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840= |
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= |
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= |
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
@ -0,0 +1,43 @@ |
||||
package ui |
||||
|
||||
import "cli-mon/yabl" |
||||
|
||||
var fNewCIS = func(packet *yabl.ContactorInternalState) *contactorType { |
||||
return &contactorType{state: packet} |
||||
} |
||||
var fGetCIS = func(c *contactorType) *yabl.ContactorInternalState { |
||||
return c.state |
||||
} |
||||
var fSetCIS = func(c *contactorType, packet *yabl.ContactorInternalState) { |
||||
c.state = packet |
||||
} |
||||
|
||||
var fNewCES = func(packet *yabl.ContactorInternalErrors) *contactorType { |
||||
return &contactorType{errors: packet} |
||||
} |
||||
var fGetCES = func(c *contactorType) *yabl.ContactorInternalErrors { |
||||
return c.errors |
||||
} |
||||
var fSetCES = func(c *contactorType, packet *yabl.ContactorInternalErrors) { |
||||
c.errors = packet |
||||
} |
||||
|
||||
var fNewPPE = func(packet *yabl.PuPresentEnergy) *connectorType { |
||||
return &connectorType{energy: packet} |
||||
} |
||||
var fGetPPE = func(c *connectorType) *yabl.PuPresentEnergy { |
||||
return c.energy |
||||
} |
||||
var fSetPPE = func(c *connectorType, packet *yabl.PuPresentEnergy) { |
||||
c.energy = packet |
||||
} |
||||
|
||||
var fNewPP = func(packet *yabl.PuPeriphery) *connectorType { |
||||
return &connectorType{state: packet} |
||||
} |
||||
var fGetPP = func(c *connectorType) *yabl.PuPeriphery { |
||||
return c.state |
||||
} |
||||
var fSetPP = func(c *connectorType, packet *yabl.PuPeriphery) { |
||||
c.state = packet |
||||
} |
@ -0,0 +1,81 @@ |
||||
package ui |
||||
|
||||
import ( |
||||
"cli-mon/yabl" |
||||
"github.com/gizak/termui/v3/widgets" |
||||
"reflect" |
||||
) |
||||
|
||||
type connectorType struct { |
||||
energy *yabl.PuPresentEnergy |
||||
state *yabl.PuPeriphery |
||||
} |
||||
|
||||
type contactorType struct { |
||||
state *yabl.ContactorInternalState |
||||
errors *yabl.ContactorInternalErrors |
||||
} |
||||
|
||||
type modelTypes interface { |
||||
*connectorType | *contactorType |
||||
} |
||||
|
||||
type dataTypes interface { |
||||
*yabl.PuPresentEnergy | *yabl.PuPeriphery | *yabl.ContactorInternalState | *yabl.ContactorInternalErrors |
||||
} |
||||
|
||||
type fNewType[T modelTypes, V dataTypes] func(e V) T |
||||
type fGetType[T modelTypes, V dataTypes] func(m T) V |
||||
type fSetType[T modelTypes, V dataTypes] func(m T, e V) |
||||
|
||||
var connectors = make([]*connectorType, connectorsCount+1) |
||||
var contactors = make([]*contactorType, contactorsCount+1) |
||||
|
||||
var connUI [connectorsCount + 1]*widgets.Paragraph |
||||
var contUI [contactorsCount + 1]*widgets.Paragraph |
||||
var contactorsErr *widgets.Paragraph |
||||
|
||||
func process(message yabl.Packet) { |
||||
if message == nil { |
||||
return |
||||
} |
||||
|
||||
unitId := message.GetUnitId() |
||||
|
||||
switch msg := message.(type) { |
||||
case *yabl.ContactorInternalState: |
||||
if updateOrNew(contactors, unitId, msg, fNewCIS, fGetCIS, fSetCIS) { |
||||
updateContactorsView(unitId) |
||||
} |
||||
|
||||
case *yabl.ContactorInternalErrors: |
||||
if updateOrNew(contactors, unitId, msg, fNewCES, fGetCES, fSetCES) { |
||||
updateContactorsStateView() |
||||
} |
||||
|
||||
case *yabl.PuPeriphery: |
||||
if updateOrNew(connectors, unitId, msg, fNewPP, fGetPP, fSetPP) { |
||||
updateConnectorsView(unitId) |
||||
} |
||||
|
||||
case *yabl.PuPresentEnergy: |
||||
if updateOrNew(connectors, unitId, msg, fNewPPE, fGetPPE, fSetPPE) { |
||||
updateConnectorsView(unitId) |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
func updateOrNew[T modelTypes, V dataTypes](array []T, id uint, msg V, fNew fNewType[T, V], fGet fGetType[T, V], fSet fSetType[T, V]) bool { |
||||
if model := array[id]; model != nil { |
||||
if element := fGet(model); !reflect.DeepEqual(element, msg) { |
||||
fSet(model, msg) |
||||
return true |
||||
} |
||||
} else { |
||||
model = fNew(msg) |
||||
array[id] = model |
||||
return true |
||||
} |
||||
return false |
||||
} |
@ -0,0 +1,88 @@ |
||||
package ui |
||||
|
||||
import ( |
||||
"cli-mon/yabl" |
||||
ui "github.com/gizak/termui/v3" |
||||
"github.com/gizak/termui/v3/widgets" |
||||
"log" |
||||
"time" |
||||
) |
||||
|
||||
const headerHeight int = 4 |
||||
const contactorsCount = 18 |
||||
const connectorsCount = 6 |
||||
|
||||
func InitCliApp(events <-chan yabl.Packet) { |
||||
if err := ui.Init(); err != nil { |
||||
log.Fatalf("failed to initialize termui: %v", err) |
||||
} |
||||
defer ui.Close() |
||||
|
||||
var connectorsTab *ui.Grid |
||||
//var contactorsErr *widgets.Paragraph
|
||||
var contactorsStateTab *ui.Grid |
||||
var header *widgets.Paragraph |
||||
|
||||
tabpane := widgets.NewTabPane("Connectors", "Contactors States", "Contactors Errors") |
||||
tabpane.SetRect(0, 1, 150, 4) |
||||
tabpane.Border = true |
||||
|
||||
header = newHeader() |
||||
connectorsTab = newConnectorsView() |
||||
contactorsStateTab = newContactorsView() |
||||
contactorsErr = newContactorsErrView() |
||||
|
||||
renderTab := func() { |
||||
switch tabpane.ActiveTabIndex { |
||||
case 0: |
||||
termWidth, termHeight := ui.TerminalDimensions() |
||||
connectorsTab.SetRect(0, headerHeight, termWidth, termHeight) |
||||
ui.Render(connectorsTab) |
||||
case 1: |
||||
termWidth, termHeight := ui.TerminalDimensions() |
||||
contactorsStateTab.SetRect(0, headerHeight, termWidth, termHeight) |
||||
ui.Render(contactorsStateTab) |
||||
case 2: |
||||
ui.Render(contactorsErr) |
||||
} |
||||
} |
||||
|
||||
ui.Render(header, tabpane, connectorsTab) |
||||
|
||||
uiEvents := ui.PollEvents() |
||||
ticker := time.NewTicker(2 * time.Second).C |
||||
for { |
||||
select { |
||||
case e := <-uiEvents: |
||||
switch e.ID { // event string/identifier
|
||||
case "q", "<C-c>": // press 'q' or 'C-c' to quit
|
||||
return |
||||
case "<MouseLeft>": |
||||
//payload := e.Payload.(ui.Mouse)
|
||||
//x, y := payload.X, payload.Y
|
||||
//p.Text = fmt.Sprintf("X = %d, Y = %d", x, y)
|
||||
case "<Left>": |
||||
tabpane.FocusLeft() |
||||
ui.Clear() |
||||
ui.Render(header, tabpane) |
||||
renderTab() |
||||
case "<Right>": |
||||
tabpane.FocusRight() |
||||
ui.Clear() |
||||
ui.Render(header, tabpane) |
||||
renderTab() |
||||
} |
||||
|
||||
switch e.Type { |
||||
case ui.KeyboardEvent: // handle all key presses
|
||||
//eventID := e.ID // keypress string
|
||||
//p.Text = eventID
|
||||
} |
||||
// use Go's built-in tickers for updating and drawing data
|
||||
case <-ticker: |
||||
renderTab() |
||||
case message := <-events: |
||||
process(message) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,224 @@ |
||||
package ui |
||||
|
||||
import ( |
||||
"cli-mon/yabl" |
||||
"fmt" |
||||
ui "github.com/gizak/termui/v3" |
||||
"github.com/gizak/termui/v3/widgets" |
||||
) |
||||
|
||||
func newHeader() *widgets.Paragraph { |
||||
widget := widgets.NewParagraph() |
||||
widget.Text = "Press q to quit, Press <Left> or <Right> to switch tabs" |
||||
widget.SetRect(0, 0, 50, 1) |
||||
widget.Border = false |
||||
widget.TextStyle.Bg = ui.ColorBlue |
||||
return widget |
||||
} |
||||
|
||||
func newConnectorsView() *ui.Grid { |
||||
grid := ui.NewGrid() |
||||
|
||||
termWidth, termHeight := ui.TerminalDimensions() |
||||
grid.SetRect(0, headerHeight, termWidth, termHeight) |
||||
|
||||
var conn [connectorsCount + 1]*widgets.Paragraph //ui.GridItem
|
||||
|
||||
for i := 0; i <= connectorsCount; i++ { |
||||
p := widgets.NewParagraph() |
||||
p.Text = fmt.Sprintf("No data: #%2.d", i) |
||||
p.Title = fmt.Sprintf("Connector: #%2.d", i) |
||||
//p.SetRect(0, 0, 50, 30)
|
||||
p.TextStyle.Bg = ui.ColorBlue |
||||
p.TextStyle.Fg = ui.ColorWhite |
||||
p.BorderStyle.Fg = ui.ColorYellow |
||||
p.Border = true |
||||
|
||||
connUI[i] = p |
||||
conn[i] = p //ui.NewCol(1.0/6, p)
|
||||
} |
||||
|
||||
grid.Set( |
||||
ui.NewRow( |
||||
1.0/2, |
||||
ui.NewCol(1.0/4, conn[1]), |
||||
ui.NewCol(1.0/4, conn[2]), |
||||
ui.NewCol(1.0/4, conn[3]), |
||||
ui.NewCol(1.0/4, conn[4]), |
||||
), |
||||
ui.NewRow( |
||||
1.0/2, |
||||
ui.NewCol(1.0/4, conn[5]), |
||||
ui.NewCol(1.0/4, conn[6]), |
||||
ui.NewCol(1.0/4, conn[0]), |
||||
), |
||||
) |
||||
|
||||
return grid |
||||
} |
||||
|
||||
func newContactorsView() *ui.Grid { |
||||
grid := ui.NewGrid() |
||||
|
||||
termWidth, termHeight := ui.TerminalDimensions() |
||||
grid.SetRect(0, headerHeight, termWidth, termHeight) |
||||
|
||||
var cont [contactorsCount + 1]ui.GridItem |
||||
|
||||
//var contactors = make([]ui.GridItem, contactorsCount)
|
||||
for i := 1; i <= contactorsCount; i++ { |
||||
p := widgets.NewParagraph() |
||||
p.Text = fmt.Sprintf("No data: #%2.d", i) |
||||
p.Title = fmt.Sprintf("Contactor: #%2.d", i) |
||||
p.Border = false |
||||
p.TextStyle.Bg = ui.ColorBlue |
||||
p.TextStyle.Fg = ui.ColorWhite |
||||
p.BorderStyle.Fg = ui.ColorYellow |
||||
p.Border = true |
||||
|
||||
contUI[i] = p |
||||
cont[i] = ui.NewCol(1.0/6, p) |
||||
} |
||||
|
||||
grid.Set( |
||||
ui.NewRow( |
||||
1.0/3, |
||||
cont[1], |
||||
cont[2], |
||||
cont[3], |
||||
cont[4], |
||||
cont[5], |
||||
cont[6], |
||||
), |
||||
ui.NewRow( |
||||
1.0/3, |
||||
cont[7], |
||||
cont[8], |
||||
cont[9], |
||||
cont[10], |
||||
cont[11], |
||||
cont[12], |
||||
), |
||||
ui.NewRow( |
||||
1.0/3, |
||||
cont[13], |
||||
cont[14], |
||||
cont[15], |
||||
cont[16], |
||||
cont[17], |
||||
cont[18], |
||||
)) |
||||
|
||||
//ui.NewRow(1, ui.InterfaceSlice(contactors)...)
|
||||
grid.Set() |
||||
|
||||
return grid |
||||
} |
||||
|
||||
func newContactorsErrView() *widgets.Paragraph { |
||||
p := widgets.NewParagraph() |
||||
p.Text = "No data\n" |
||||
p.Title = "Contactors Errors" |
||||
p.SetRect(5, 5, 55, 15) |
||||
p.BorderStyle.Fg = ui.ColorYellow |
||||
|
||||
return p |
||||
} |
||||
|
||||
func updateContactorsView(unitId uint) { |
||||
p := contUI[unitId] |
||||
s := contactors[unitId].state |
||||
|
||||
if s == nil || p == nil { |
||||
return |
||||
} |
||||
|
||||
p.Text = fmt.Sprintf( |
||||
"BoardReady: %s\nContactorOn: %s\nUnexpectedState: %s\nIsolated: %s\nDebug: %s", |
||||
s.ContactorReady, |
||||
s.ContactorOn, |
||||
s.UnexpectedState, |
||||
s.Isolated, |
||||
s.DebugEnabled) |
||||
|
||||
if s.ContactorReady == yabl.BOARD_READY_OK { |
||||
p.TextStyle.Bg = ui.ColorClear |
||||
} else { |
||||
p.TextStyle.Bg = ui.ColorRed |
||||
} |
||||
|
||||
if s.ContactorOn == yabl.ON { |
||||
p.TitleStyle.Bg = ui.ColorBlue |
||||
} else { |
||||
p.TitleStyle.Bg = ui.ColorClear |
||||
} |
||||
} |
||||
|
||||
func updateConnectorsView(unitId uint) { |
||||
p := connUI[unitId] |
||||
s := connectors[unitId].state |
||||
e := connectors[unitId].energy |
||||
|
||||
if s == nil || e == nil { |
||||
return |
||||
} |
||||
|
||||
fstring := "ConnectorInsert %s\n" + |
||||
"ContactorOn %s\n" + |
||||
"ConnectorLocked %s\n" + |
||||
"LineLevel %s\n" + |
||||
"IsolationState %s\n" + |
||||
"ChargingAllowed %s\n" + |
||||
"PwmEnabled %s\n" + |
||||
"CpLineVoltage %s\n" + |
||||
"V2GMode %s\n" + |
||||
"VoltageBefore %s\n" + |
||||
"VoltageAfter %s\n" + |
||||
"PresentCurrent %s" |
||||
|
||||
p.Text = fmt.Sprintf( |
||||
fstring, |
||||
s.ConnectorInsert, |
||||
s.ContactorOn, |
||||
s.ConnectorLocked, |
||||
s.CpLineLevel, |
||||
s.IsolationState, |
||||
s.ChargingAllowed, |
||||
s.PwmEnabled, |
||||
s.CpLineVoltage, |
||||
e.V2GMode, |
||||
e.VoltageBefore, |
||||
e.VoltageAfter, |
||||
e.PresentCurrent) |
||||
} |
||||
|
||||
func updateContactorsStateView() { |
||||
p := contactorsErr |
||||
|
||||
if contactors[1] == nil { |
||||
return |
||||
} |
||||
|
||||
e := contactors[1].errors |
||||
|
||||
if e == nil { |
||||
return |
||||
} |
||||
|
||||
p.Text = fmt.Sprintf( |
||||
"BoardReady: %s\nOtherError: %s\nGroupChanged: %s\nUnexpectedFormation: %s\nCpuNotReady: %s\nPuNotReady: %s\nDebug: %s", |
||||
e.BoardReady, |
||||
e.OtherError, |
||||
e.ContactorGroupChanged, |
||||
e.UnexpectedFormation, |
||||
e.CpuNotReady, |
||||
e.PuNotReady, |
||||
e.Debug, |
||||
) |
||||
|
||||
if e.BoardReady == yabl.BOARD_READY_OK { |
||||
p.TextStyle.Bg = ui.ColorClear |
||||
} else { |
||||
p.TextStyle.Bg = ui.ColorRed |
||||
} |
||||
} |
@ -0,0 +1,280 @@ |
||||
package yabl |
||||
|
||||
import ( |
||||
can "cli-mon/can" |
||||
"encoding/binary" |
||||
) |
||||
|
||||
const ( |
||||
CONTACTOR_MAX = 18 + 1 |
||||
CONNECTOR_MAX = 6 + 1 |
||||
ALL_BITS = 0xFFFFFFFFFFFFFFFF |
||||
UNIT_ID_OFFSET = 12 |
||||
UNIT_ID_MASK = 0b111111111111 << UNIT_ID_OFFSET |
||||
ACTION_ID_MASK = 0b111111111111 |
||||
BOARD_READY_OK BoardReadyType = 0 |
||||
BOARD_READY_INFO BoardReadyType = 1 |
||||
BOARD_READY_WARNING BoardReadyType = 2 |
||||
BOARD_READY_DEBUG BoardReadyType = 3 |
||||
BOARD_READY_DEP_ERROR BoardReadyType = 4 |
||||
BOARD_READY_ERROR BoardReadyType = 5 |
||||
BOARD_READY_CRITICAL BoardReadyType = 6 |
||||
OFF BooleanType = 0 |
||||
ON BooleanType = 1 |
||||
NO_ERROR ErrorType = 0 |
||||
ERROR ErrorType = 1 |
||||
OTHER_NO_ERROR ContactorInternalOtherErrorType = 0 |
||||
NO_PENDING_CHANGES ContactorGroupChangedType = 0 |
||||
INITIALIZED_CHANGE ContactorGroupChangedType = 1 |
||||
CHANGE_IN_PROGRESS ContactorGroupChangedType = 2 |
||||
V2G_MODE_G2V V2GModeType = 0 |
||||
V2G_MODE_V2G V2GModeType = 1 |
||||
V2G_MODE_INV V2GModeType = 2 |
||||
NOT_ENABLE CpLineLevelType = 0 |
||||
TWELVE CpLineLevelType = 1 |
||||
NINE CpLineLevelType = 2 |
||||
SIX CpLineLevelType = 3 |
||||
THREE CpLineLevelType = 4 |
||||
MINUS_TWELVE CpLineLevelType = 5 |
||||
UNKNOWN IsolationStateType = 0 |
||||
ONGOING IsolationStateType = 1 |
||||
PASSED IsolationStateType = 2 |
||||
WARNING IsolationStateType = 3 |
||||
FAILED IsolationStateType = 4 |
||||
) |
||||
|
||||
type Packet interface { |
||||
GetUnitId() uint |
||||
} |
||||
|
||||
type ContactorInternalState struct { |
||||
UnitId uint |
||||
ContactorReady BoardReadyType |
||||
ContactorOn BooleanType |
||||
UnexpectedState ErrorType |
||||
Isolated ErrorType |
||||
DebugEnabled BooleanType |
||||
} |
||||
|
||||
type ContactorInternalErrors struct { |
||||
UnitId uint |
||||
BoardReady BoardReadyType |
||||
OtherError ContactorInternalOtherErrorType |
||||
ContactorGroupChanged ContactorGroupChangedType |
||||
UnexpectedFormation ContactorInternalOtherErrorType |
||||
CpuNotReady ErrorType |
||||
PuNotReady ErrorType |
||||
Debug BooleanType |
||||
} |
||||
|
||||
type PuPresentEnergy struct { |
||||
UnitId uint |
||||
V2GMode V2GModeType |
||||
VoltageBefore Voltage11BitType |
||||
VoltageAfter Voltage11BitType |
||||
PresentCurrent Current10BitType |
||||
} |
||||
|
||||
type PuPeriphery struct { |
||||
UnitId uint |
||||
ConnectorInsert BooleanType |
||||
ContactorOn BooleanType |
||||
ConnectorLocked BooleanType |
||||
CpLineLevel CpLineLevelType |
||||
IsolationState IsolationStateType |
||||
ChargingAllowed BooleanType |
||||
PwmEnabled BooleanType |
||||
CpLineVoltage Voltage9BitType |
||||
} |
||||
|
||||
type BoardReadyType uint |
||||
type BooleanType uint |
||||
type ErrorType uint |
||||
type ContactorInternalOtherErrorType uint |
||||
type ContactorGroupChangedType uint |
||||
type V2GModeType uint |
||||
type Voltage11BitType uint |
||||
type Current10BitType uint |
||||
type CpLineLevelType uint |
||||
type IsolationStateType uint |
||||
type Voltage9BitType float32 |
||||
|
||||
func StartProtocolParsing(frames <-chan *can.CanFrame) <-chan Packet { |
||||
result := make(chan Packet) |
||||
|
||||
go func() { |
||||
defer close(result) |
||||
var contactorsFirstState [CONTACTOR_MAX]bool |
||||
var contactorsFirstErr [CONTACTOR_MAX]bool |
||||
var puFirst [CONNECTOR_MAX]bool |
||||
var puPeripheryFirst [CONNECTOR_MAX]bool |
||||
|
||||
for frame := range frames { |
||||
if frames == nil { |
||||
continue |
||||
} |
||||
|
||||
var unitId uint = uint((frame.CanId & UNIT_ID_MASK) >> UNIT_ID_OFFSET) |
||||
|
||||
switch { |
||||
case frame.CanId&ACTION_ID_MASK == can.CAN_ID_071: |
||||
changed := false |
||||
var model = ContactorInternalState{UnitId: unitId} |
||||
if val, ok := get(0, 3, frame.Payload); ok && model.ContactorReady != BoardReadyType(val) { |
||||
model.ContactorReady = BoardReadyType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(3, 2, frame.Payload); ok && model.ContactorOn != BooleanType(val) { |
||||
model.ContactorOn = BooleanType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(5, 2, frame.Payload); ok && model.UnexpectedState != ErrorType(val) { |
||||
model.UnexpectedState = ErrorType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(7, 2, frame.Payload); ok && model.Isolated != ErrorType(val) { |
||||
model.Isolated = ErrorType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(9, 2, frame.Payload); ok && model.DebugEnabled != BooleanType(val) { |
||||
model.DebugEnabled = BooleanType(val) |
||||
changed = true |
||||
} |
||||
if changed || !contactorsFirstState[unitId] { |
||||
contactorsFirstState[unitId] = true |
||||
result <- &model |
||||
} |
||||
case frame.CanId&ACTION_ID_MASK == can.CAN_ID_073: |
||||
changed := false |
||||
model := ContactorInternalErrors{UnitId: unitId} |
||||
if val, ok := get(0, 3, frame.Payload); ok && model.BoardReady != BoardReadyType(val) { |
||||
model.BoardReady = BoardReadyType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(3, 4, frame.Payload); ok && model.OtherError != ContactorInternalOtherErrorType(val) { |
||||
model.OtherError = ContactorInternalOtherErrorType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(7, 3, frame.Payload); ok && model.ContactorGroupChanged != ContactorGroupChangedType(val) { |
||||
model.ContactorGroupChanged = ContactorGroupChangedType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(10, 4, frame.Payload); ok && model.UnexpectedFormation != ContactorInternalOtherErrorType(val) { |
||||
model.UnexpectedFormation = ContactorInternalOtherErrorType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(14, 2, frame.Payload); ok && model.CpuNotReady != ErrorType(val) { |
||||
model.CpuNotReady = ErrorType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(16, 2, frame.Payload); ok && model.PuNotReady != ErrorType(val) { |
||||
model.PuNotReady = ErrorType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(18, 2, frame.Payload); ok && model.Debug != BooleanType(val) { |
||||
model.Debug = BooleanType(val) |
||||
changed = true |
||||
} |
||||
if changed || !contactorsFirstErr[unitId] { |
||||
contactorsFirstErr[unitId] = true |
||||
result <- &model |
||||
} |
||||
case frame.CanId&ACTION_ID_MASK == can.CAN_ID_021: |
||||
changed := false |
||||
model := PuPresentEnergy{UnitId: unitId} |
||||
if val, ok := get(0, 2, frame.Payload); ok && model.V2GMode != V2GModeType(val) { |
||||
model.V2GMode = V2GModeType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(2, 11, frame.Payload); ok && model.VoltageBefore != Voltage11BitType(val) { |
||||
model.VoltageBefore = Voltage11BitType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(13, 11, frame.Payload); ok && model.VoltageAfter != Voltage11BitType(val) { |
||||
model.VoltageAfter = Voltage11BitType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(24, 10, frame.Payload); ok && model.PresentCurrent != Current10BitType(val) { |
||||
model.PresentCurrent = Current10BitType(val) |
||||
changed = true |
||||
} |
||||
if changed || !puFirst[unitId] { |
||||
puFirst[unitId] = true |
||||
result <- &model |
||||
} |
||||
case frame.CanId&ACTION_ID_MASK == can.CAN_ID_022: |
||||
changed := false |
||||
model := PuPeriphery{UnitId: unitId} |
||||
if val, ok := get(0, 2, frame.Payload); ok && model.ConnectorInsert != BooleanType(val) { |
||||
model.ConnectorInsert = BooleanType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(2, 2, frame.Payload); ok && model.ContactorOn != BooleanType(val) { |
||||
model.ContactorOn = BooleanType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(4, 2, frame.Payload); ok && model.ConnectorLocked != BooleanType(val) { |
||||
model.ConnectorLocked = BooleanType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(6, 3, frame.Payload); ok && model.CpLineLevel != CpLineLevelType(val) { |
||||
model.CpLineLevel = CpLineLevelType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(9, 3, frame.Payload); ok && model.IsolationState != IsolationStateType(val) { |
||||
model.IsolationState = IsolationStateType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(12, 2, frame.Payload); ok && model.ChargingAllowed != BooleanType(val) { |
||||
model.ChargingAllowed = BooleanType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(14, 2, frame.Payload); ok && model.PwmEnabled != BooleanType(val) { |
||||
model.PwmEnabled = BooleanType(val) |
||||
changed = true |
||||
} |
||||
if val, ok := get(16, 9, frame.Payload); ok && model.CpLineVoltage != (Voltage9BitType(val)*0.1-15) { |
||||
model.CpLineVoltage = Voltage9BitType(val)*0.1 - 15 |
||||
changed = true |
||||
} |
||||
if changed || !puPeripheryFirst[unitId] { |
||||
puPeripheryFirst[unitId] = true |
||||
result <- &model |
||||
} |
||||
|
||||
} |
||||
} |
||||
}() |
||||
|
||||
return result |
||||
} |
||||
|
||||
func get(from uint, size uint, buffer []byte) (uint64, bool) { |
||||
value := binary.LittleEndian.Uint64(buffer) |
||||
|
||||
var mask uint64 = ^(ALL_BITS << size) |
||||
mask = mask << from |
||||
selected := mask & value |
||||
|
||||
if selected == mask { |
||||
return 0, false |
||||
} else { |
||||
return selected >> from, true |
||||
} |
||||
} |
||||
|
||||
func (t ContactorInternalState) GetUnitId() uint { |
||||
return t.UnitId |
||||
} |
||||
|
||||
func (t ContactorInternalErrors) GetUnitId() uint { |
||||
return t.UnitId |
||||
} |
||||
|
||||
func (t PuPresentEnergy) GetUnitId() uint { |
||||
return t.UnitId |
||||
} |
||||
|
||||
func (t PuPeriphery) GetUnitId() uint { |
||||
return t.UnitId |
||||
} |
@ -0,0 +1,129 @@ |
||||
package yabl |
||||
|
||||
import "fmt" |
||||
|
||||
func (t BooleanType) String() string { |
||||
switch t { |
||||
case ON: |
||||
return "ON" |
||||
case OFF: |
||||
return "OFF" |
||||
default: |
||||
panic("BooleanType not defended") |
||||
} |
||||
} |
||||
|
||||
func (t ErrorType) String() string { |
||||
switch t { |
||||
case NO_ERROR: |
||||
return "OK" |
||||
case ERROR: |
||||
return "ERROR" |
||||
default: |
||||
panic("ErrorType not defended") |
||||
} |
||||
} |
||||
|
||||
func (t BoardReadyType) String() string { |
||||
switch t { |
||||
case BOARD_READY_OK: |
||||
return "OK" |
||||
case BOARD_READY_INFO: |
||||
return "INFO" |
||||
case BOARD_READY_WARNING: |
||||
return "WARNING" |
||||
case BOARD_READY_DEBUG: |
||||
return "DEBUG" |
||||
case BOARD_READY_DEP_ERROR: |
||||
return "DEP_ERROR" |
||||
case BOARD_READY_ERROR: |
||||
return "ERROR" |
||||
case BOARD_READY_CRITICAL: |
||||
return "CRITICAL" |
||||
default: |
||||
panic("BoardReadyType not defended") |
||||
} |
||||
} |
||||
|
||||
func (t ContactorInternalOtherErrorType) String() string { |
||||
switch t { |
||||
case OTHER_NO_ERROR: |
||||
return "OK" |
||||
default: |
||||
return fmt.Sprintf("ERROR_CODE_%d", t) |
||||
} |
||||
} |
||||
|
||||
func (t ContactorGroupChangedType) String() string { |
||||
switch t { |
||||
case NO_PENDING_CHANGES: |
||||
return "NO_PENDING" |
||||
case INITIALIZED_CHANGE: |
||||
return "INITIALIZED" |
||||
case CHANGE_IN_PROGRESS: |
||||
return "IN_PROGRESS" |
||||
default: |
||||
panic("ContactorGroupChangedType not defended") |
||||
} |
||||
} |
||||
|
||||
func (t V2GModeType) String() string { |
||||
switch t { |
||||
case V2G_MODE_G2V: |
||||
return "G2V" |
||||
case V2G_MODE_V2G: |
||||
return "V2G" |
||||
case V2G_MODE_INV: |
||||
return "INV" |
||||
default: |
||||
panic("V2GModeType not defended") |
||||
} |
||||
} |
||||
|
||||
func (t Voltage11BitType) String() string { |
||||
return fmt.Sprintf("%dV", t) |
||||
} |
||||
|
||||
func (t Current10BitType) String() string { |
||||
return fmt.Sprintf("%dA", t) |
||||
} |
||||
|
||||
func (t CpLineLevelType) String() string { |
||||
switch t { |
||||
case NOT_ENABLE: |
||||
return "NOT_ENABLE" |
||||
case TWELVE: |
||||
return "12V_NOT_CONNECTED" |
||||
case NINE: |
||||
return "9V_CONNECTED" |
||||
case SIX: |
||||
return "6V_CHARGING" |
||||
case THREE: |
||||
return "3V" |
||||
case MINUS_TWELVE: |
||||
return "-12V" |
||||
default: |
||||
panic("CpLineLevelType not defended") |
||||
} |
||||
} |
||||
|
||||
func (t IsolationStateType) String() string { |
||||
switch t { |
||||
case UNKNOWN: |
||||
return "UNKNOWN" |
||||
case ONGOING: |
||||
return "ONGOING" |
||||
case PASSED: |
||||
return "PASSED" |
||||
case WARNING: |
||||
return "WARNING" |
||||
case FAILED: |
||||
return "FAILED" |
||||
default: |
||||
panic("IsolationStateType not defended") |
||||
} |
||||
} |
||||
|
||||
func (t Voltage9BitType) String() string { |
||||
return fmt.Sprintf("%.1fV", t) |
||||
} |
Loading…
Reference in new issue