Skip to content
Permalink
demo-day
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
package main
import (
"encoding/binary"
"fmt"
"github.com/goburrow/modbus"
"sort"
"strconv"
"strings"
"sync"
"time"
)
var modbusClient = &_modbusClient{
modbus.NewClient(nil),
sync.Mutex{},
sync.WaitGroup{},
_registers,
}
type _modbusClient struct {
conn modbus.Client
lock sync.Mutex
wait sync.WaitGroup
registers map[int][2]string
}
func (m *_modbusClient) start() {
m.wait.Add(1)
go m.connect()
}
func (m *_modbusClient) connect() {
handler := modbus.NewRTUClientHandler("/dev/ttyUSB0")
handler.BaudRate = 19200
handler.DataBits = 8
handler.Parity = "N"
handler.StopBits = 1
handler.SlaveId = 1
handler.Timeout = 5 * time.Second
if err := handler.Connect(); err != nil {
fmt.Printf("Error connecting to modbus RTU client: %v\n", err)
}
m.conn = modbus.NewClient(handler)
m.wait.Done()
}
func (m *_modbusClient) readHoldingRegisters(start uint16, num uint16) []byte {
m.wait.Wait()
m.lock.Lock()
defer m.lock.Unlock()
data, err := m.conn.ReadHoldingRegisters(start, num)
if err != nil {
fmt.Printf("Error reading %v holding registers starting with %v: %v\n", start, num, err)
return []byte{}
} else {
return data
}
}
func (m *_modbusClient) readAllRegisterData() map[string]StampedReading {
m.wait.Wait() // Wait for client to start up
var keys []int // Array for register numbers
for k := range m.registers {
keys = append(keys, k)
}
sort.Ints(keys) // Make sure it's ascending
results := make(map[string]StampedReading) // Result map
intervals := m.readsForAllRegisters() // Modbus reading intervals
var data []byte // Data from modbus
m.lock.Lock() // One reader at a time to be safe
for _, interval := range intervals { // Do readings for all intervals
start := uint16(interval[0])
number := uint16(interval[1])
reading, err := m.conn.ReadHoldingRegisters(start, number) // Read the registers
if err != nil { // Make sure it worked
fmt.Printf("Error reading %v registers starting at %v: %v\n", number, start, err)
continue
}
data = append(data, reading...) // Accumulate the data
}
m.lock.Unlock()
timestamp := int(time.Now().Unix())
for _, interval := range intervals { // Do mappings for all intervals
start := uint16(interval[0])
number := uint16(interval[1])
for r := 0; r < int(start+number); r++ { // Check all the registers in this range
if val, found := m.registers[r]; found { // If we have a metric for this, add it to results
switch val[1] {
case WORD:
results[val[0]] = StampedReading{timestamp, int32(binary.LittleEndian.Uint16(data[2*r : 2*r+2]))}
case SWORD:
results[val[0]] = StampedReading{timestamp, int32(int16(binary.LittleEndian.Uint16(data[2*r : 2*r+2])))}
}
}
}
}
return results
}
func (m *_modbusClient) readsForAllRegisters() [][2]int {
var reads [][2]int
var keys []int
for k := range m.registers {
keys = append(keys, k)
}
sort.Ints(keys)
startReg := keys[0]
var i int
for i = 0; i < len(keys); i++ {
if keys[i]-startReg > 49 {
reads = append(reads, [2]int{startReg, keys[i-1] - startReg + 1})
startReg = keys[i]
}
}
reads = append(reads, [2]int{startReg, keys[i-1] - startReg + 1})
return reads
}
func reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func (m *_modbusClient) getDigitalInputs() string {
data := m.readHoldingRegisters(uint16(76), uint16(2))
binstr := make([]string, len(data))
for i := 0; i < len(data); i += 2 {
binstr[i] = reverse(fmt.Sprintf("%08s", strconv.FormatInt(int64(data[i+1]), 2)))
binstr[i+1] = reverse(fmt.Sprintf("%08s", strconv.FormatInt(int64(data[i]), 2)))
}
return strings.Join(binstr, "")
}
func (m *_modbusClient) getDigitalUpdates() map[string]int {
inputs := m.getDigitalInputs()
inputMap := make(map[string]int)
for k, v := range _inputs {
input := inputs[k] - 48
inputMap[v] = int(input)
}
if len(_lastInput) == 0 {
_lastInput = inputMap
return inputMap
} else {
updateMap := inputMap
for k, newv := range inputMap {
oldv, found := _lastInput[k]
if found {
if newv == oldv {
delete(updateMap, k)
}
}
}
for k, v := range updateMap {
_lastInput[k] = v
}
if len(updateMap) > 0 {
fmt.Printf("Digital Input Updates: %v\n", updateMap)
}
return updateMap
}
}
func (m *_modbusClient) getStatusCodes() string {
data := m.readHoldingRegisters(uint16(1499), uint16(16))
binstr := make([]string, len(data))
for i := 0; i < len(data); i += 2 {
binstr[i] = reverse(fmt.Sprintf("%08s", strconv.FormatInt(int64(data[i+1]), 2)))
binstr[i+1] = reverse(fmt.Sprintf("%08s", strconv.FormatInt(int64(data[i]), 2)))
}
return strings.Join(binstr, "")
}
func (m *_modbusClient) getStatusUpdates() map[string]int {
statuses := m.getStatusCodes()
statusMap := make(map[string]int)
for k, v := range _statuses {
status := statuses[k] - 48
statusMap[v] = int(status)
}
if len(_lastStatus) == 0 {
_lastStatus = statusMap
return statusMap
} else {
updateMap := statusMap
for k, newv := range statusMap {
oldv, found := _lastStatus[k]
if found {
if newv == oldv {
delete(updateMap, k)
}
}
}
for k, v := range updateMap {
_lastStatus[k] = v
}
if len(updateMap) > 0 {
fmt.Printf("Status updates: %v\n", updateMap)
}
return updateMap
}
}
var _registers = map[int][2]string{
0: {"l1.l2.voltage", WORD},
1: {"l2.l3.voltage", WORD},
2: {"l3.l1.voltage", WORD},
3: {"l1.l0.voltage", WORD},
4: {"l2.l0.voltage", WORD},
5: {"l3.l0.voltage", WORD},
6: {"l1.current", WORD},
7: {"l2.current", WORD},
8: {"l3.current", WORD},
9: {"frequency", WORD},
10: {"total.kw", WORD},
11: {"rate.kw", WORD},
12: {"total.pf", SWORD},
13: {"l1.kw", WORD},
14: {"l1.pf", SWORD},
15: {"l2.kw", WORD},
16: {"l2.pf", SWORD},
17: {"l3.kw", WORD},
18: {"l3.pf", SWORD},
19: {"total.kvar", SWORD},
20: {"l1.kvar", SWORD},
21: {"l2.kvar", SWORD},
22: {"l3.kvar", SWORD},
23: {"total.kva", WORD},
24: {"l1.kva", WORD},
25: {"l2.kva", WORD},
26: {"l3.kva", WORD},
32: {"oil.pressure", SWORD},
33: {"coolant.temp", SWORD},
34: {"engine.rpm", WORD},
35: {"battery.voltage", WORD},
36: {"fuel.pressure", WORD},
37: {"fuel.temp", SWORD},
38: {"fuel.rate", WORD},
40: {"coolant.pressure", WORD},
41: {"coolant.level", WORD},
42: {"oil.temp", SWORD},
43: {"oil.level", WORD},
44: {"crankcase.pressure", WORD},
45: {"ambient.temp", SWORD},
46: {"ecm.battery.voltage", WORD},
48: {"intake.temp", SWORD},
49: {"intake.pressure", WORD},
}
var _statuses = map[int]string{
0: "fault.emstop",
1: "fault.overspeed",
2: "fault.overcrank",
5: "fault.coolant.temp.low",
6: "fault.fuel.low",
7: "fault.engine.temp.high",
8: "fault.oil.pressure.low",
9: "status.notinauto",
11: "fault.batt.volt.low",
12: "fault.batt.volt.high",
14: "status.system.ready",
19: "fault.coolant.level.low",
27: "fault.crank.volt.low",
34: "status.eps",
39: "status.gen.running",
207: "fault.common",
}
var _inputs = map[int]string{
3: "fault.overcrank", // digital input 1
4: "fault.fuel.low",
5: "fault.engine.temp.high",
6: "fault.common",
7: "status.gen.running",
8: "fault.overspeed",
}
var _lastStatus = map[string]int{}
var _lastInput = map[string]int{}