2024-04-13 20:29:31 +02:00
exports . id = 'modbus_reader' ;
exports . title = 'Modbus reader' ;
exports . version = '2.0.0' ;
exports . group = 'Worksys' ;
exports . color = '#2134B0' ;
2024-05-07 16:28:43 +02:00
exports . output = [ "red" , "white" , "yellow" ] ;
2024-04-13 20:29:31 +02:00
exports . click = false ;
exports . author = 'Rastislav Kovac' ;
exports . icon = 'bolt' ;
exports . readme = `
Modbus requests to modbus devices ( electromer , twilight sensor , thermometer .
Component keeps running arround deviceConfig array in "timeoutInterval" intervals . Array items are objects with single modbus devices .
Everything is sent to dido _controller . If requests to device fail ( all registers must fail to send NOK status ) , we send "NOK-'device'" status to dido _controller .
This device needs to be configured in dido _controller ! ! ! Double check if it is . In dido _controller we calculate final status and all values with status are pushed to tb .
` ;
2024-09-09 12:32:34 +02:00
const modbus = require ( 'jsmodbus' ) ;
const SerialPort = require ( 'serialport' ) ;
2024-04-13 20:29:31 +02:00
const { timeoutInterval , deviceConfig } = require ( "../databases/modbus_config" ) ;
const { sendNotification } = require ( './helper/notification_reporter' ) ;
2024-05-07 16:28:43 +02:00
const DELAY _BETWEEN _DEVICES = 10000 ;
2024-05-21 15:49:42 +02:00
const SEND _TO = {
2024-04-13 20:29:31 +02:00
debug : 0 ,
dido _controller : 1 ,
2024-05-07 16:28:43 +02:00
tb : 2
2024-04-13 20:29:31 +02:00
} ;
//to handle NOK and OK sendNotifications s
const numberOfNotResponding = { } ;
let tbName = null ;
2024-09-09 12:32:34 +02:00
let mainSocket ;
2024-04-13 20:29:31 +02:00
exports . install = function ( instance ) {
class SocketWithClients {
constructor ( ) {
this . stream = null ;
this . socket = null ;
this . clients = { } ;
this . allValues = { } ;
this . errors = 0 ;
this . index = 0 ;
this . timeoutInterval = 5000 ;
2024-05-21 15:49:42 +02:00
// we need to go always around for all devices. So we need index value, device address, as well as number of registers for single device
this . deviceAddress = null ; // device address (1 - EM340 and 2 for twilight_sensor)
this . indexInDeviceConfig = 0 ; // first item in deviceConfig
2024-04-13 20:29:31 +02:00
this . lengthOfActualDeviceStream = null ;
this . device = null ;
2024-09-09 12:32:34 +02:00
// lampSwitchNotification helper variables
this . onNotificationSent = false ;
this . offNotificationSent = false ;
2024-04-13 20:29:31 +02:00
this . startSocket ( ) ;
}
startSocket = ( ) => {
let obj = this ;
this . socket = new SerialPort ( "/dev/ttymxc0" , {
baudRate : 9600 ,
} )
// we create a client for every deviceAddress ( = address) in list and push them into dictionary
for ( let i = 0 ; i < deviceConfig . length ; i ++ )
{
2024-05-21 15:49:42 +02:00
this . clients [ deviceConfig [ i ] . deviceAddress ] = new modbus . client . RTU ( this . socket , deviceConfig [ i ] . deviceAddress , 2000 ) ; // 2000 is timeout in register request, default is 5000, which is too long
2024-04-13 20:29:31 +02:00
}
this . socket . on ( 'error' , function ( e ) {
console . log ( 'socket connection error' , e ) ;
if ( e . code == 'ECONNREFUSED' || e . code == 'ECONNRESET' ) {
console . log ( exports . title + ' Waiting 10 seconds before trying to connect again' ) ;
setTimeout ( obj . startSocket , 10000 ) ;
}
} ) ;
this . socket . on ( 'close' , function ( ) {
console . log ( 'Socket connection closed ' + exports . title + ' Waiting 10 seconds before trying to connect again' ) ;
setTimeout ( obj . startSocket , 10000 ) ;
} ) ;
this . socket . on ( 'open' , function ( ) {
console . log ( "socket connected" ) ;
obj . getActualStreamAndDevice ( ) ;
2024-05-07 16:28:43 +02:00
obj . timeoutInterval = timeoutInterval - DELAY _BETWEEN _DEVICES ; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES
2024-04-13 20:29:31 +02:00
} )
} ;
getActualStreamAndDevice = ( ) => {
const dev = deviceConfig [ this . indexInDeviceConfig ] ;
this . index = 0 ;
this . errors = 0 ;
this . stream = dev . stream ;
this . lengthOfActualDeviceStream = dev . stream . length ;
this . deviceAddress = dev . deviceAddress ; // 1 or 2 or any number
this . device = dev . device ; //em340, twilight_sensor
if ( this . indexInDeviceConfig == 0 ) setTimeout ( this . readRegisters , this . timeoutInterval ) ;
2024-05-07 16:28:43 +02:00
else setTimeout ( this . readRegisters , DELAY _BETWEEN _DEVICES ) ;
2024-04-13 20:29:31 +02:00
}
readRegisters = ( ) => {
const str = this . stream [ this . index ] ;
const register = str . register ;
const size = str . size ;
const tbAttribute = str . tbAttribute ;
let obj = this ;
this . clients [ this . deviceAddress ] . readHoldingRegisters ( register , size )
. then ( function ( resp ) {
resp = resp . response . _body . valuesAsArray ; //resp is array of length 1 or 2, f.e. [2360,0]
// console.log(deviceAddress, register, tbAttribute, resp);
//device is responding again after NOK status
if ( numberOfNotResponding . hasOwnProperty ( obj . device ) )
{
let message = "" ;
if ( obj . device == "em340" )
{
message = "electrometer_ok" ;
}
else if ( obj . device == "twilight_sensor" )
{
message = "twilight_sensor_ok" ;
}
2024-05-21 15:49:42 +02:00
message && sendNotification ( "modbus_reader: readRegisters" , tbName , message , { } , "" , SEND _TO . tb , instance ) ;
2024-04-13 20:29:31 +02:00
delete numberOfNotResponding [ obj . device ] ;
}
2024-05-21 15:49:42 +02:00
obj . transformResponse ( resp , register ) ;
2024-04-13 20:29:31 +02:00
2024-05-21 15:49:42 +02:00
//obj.errors = 0;
2024-04-13 20:29:31 +02:00
obj . index ++ ;
2024-05-07 16:28:43 +02:00
obj . readAnotherRegister ( ) ;
2024-04-13 20:29:31 +02:00
} ) . catch ( function ( ) {
2024-05-14 16:29:11 +02:00
console . log ( "errors pri citani modbus registra" , register , obj . indexInDeviceConfig , tbName , tbAttribute ) ;
2024-04-13 20:29:31 +02:00
2024-05-14 16:29:11 +02:00
obj . errors ++ ;
if ( obj . errors == obj . lengthOfActualDeviceStream )
2024-04-13 20:29:31 +02:00
{
2024-05-21 15:49:42 +02:00
instance . send ( SEND _TO . dido _controller , { status : "NOK-" + obj . device } ) ; // NOK-em340, NOK-em111, NOK-twilight_sensor, NOK-thermometer
2024-04-13 20:29:31 +02:00
//todo - neposlalo notification, ked sme vypojili twilight a neposle to do tb, ale do dido ??
if ( ! numberOfNotResponding . hasOwnProperty ( obj . device ) )
{
let message = "" ;
if ( obj . device == "twilight_sensor" )
{
message = "twilight_sensor_nok" ;
}
else if ( obj . device == "em340" )
{
message = "electrometer_nok" ;
}
2024-05-21 15:49:42 +02:00
message && sendNotification ( "modbus_reader: readingTimeouted" , tbName , message , { } , "" , SEND _TO . tb , instance ) ;
2024-04-13 20:29:31 +02:00
numberOfNotResponding [ obj . device ] = 1 ;
}
2024-05-14 16:29:11 +02:00
obj . errors = 0 ;
2024-04-13 20:29:31 +02:00
numberOfNotResponding [ obj . device ] += 1 ;
}
2024-09-22 22:18:46 +02:00
// console.error(require('util').inspect(arguments, {
// depth: null
// }))
2024-04-13 20:29:31 +02:00
2024-05-21 15:49:42 +02:00
// if reading out of device's last register returns error, we send accumulated allValues to dido_controller (if allValues are not an empty object)
if ( obj . index + 1 >= obj . lengthOfActualDeviceStream )
{
if ( ! isObjectEmpty ( obj . allValues ) ) instance . send ( SEND _TO . dido _controller , { values : obj . allValues } ) ;
obj . allValues = { } ;
}
2024-04-13 20:29:31 +02:00
obj . index ++ ;
2024-05-07 16:28:43 +02:00
obj . readAnotherRegister ( ) ;
2024-04-13 20:29:31 +02:00
} )
} ;
2024-05-07 16:28:43 +02:00
readAnotherRegister = ( ) => {
if ( this . index < this . lengthOfActualDeviceStream ) setTimeout ( this . readRegisters , 0 ) ;
else this . setNewStream ( ) ;
}
2024-05-21 15:49:42 +02:00
transformResponse = ( response , register ) => {
2024-04-13 20:29:31 +02:00
for ( let i = 0 ; i < this . lengthOfActualDeviceStream ; i ++ ) {
let a = this . stream [ i ] ;
if ( a . register === register )
{
let tbAttribute = a . tbAttribute ;
let multiplier = a . multiplier ;
let value = this . calculateValue ( response , multiplier ) ;
2024-05-21 15:49:42 +02:00
// console.log(register, tbName, tbAttribute, response, a.multiplier, value);
2024-04-13 20:29:31 +02:00
// if(tbName == undefined) return;
2024-05-21 15:49:42 +02:00
if ( this . index + 1 < this . lengthOfActualDeviceStream )
2024-04-13 20:29:31 +02:00
{
this . allValues [ tbAttribute ] = value ;
return ;
}
const values = {
... this . allValues ,
[ tbAttribute ] : value ,
} ;
this . checkNullVoltage ( values ) ;
2024-09-09 12:32:34 +02:00
this . lampSwitchNotification ( values ) ;
2024-04-13 20:29:31 +02:00
2024-05-21 15:49:42 +02:00
instance . send ( SEND _TO . dido _controller , { values : values } ) ;
2024-04-13 20:29:31 +02:00
this . allValues = { } ;
break ;
}
}
}
setNewStream = ( ) =>
{
if ( this . lengthOfActualDeviceStream == this . index )
{
if ( this . indexInDeviceConfig + 1 == deviceConfig . length )
{
this . indexInDeviceConfig = 0 ;
}
else
{
this . indexInDeviceConfig += 1 ;
}
this . getActualStreamAndDevice ( ) ;
}
}
calculateValue = ( response , multiplier ) =>
{
let value = 0 ;
let l = response . length ;
if ( l === 2 )
{
value = ( response [ 1 ] * ( 2 * * 16 ) + response [ 0 ] ) ;
if ( value >= ( 2 * * 31 ) ) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x80000000), ak vieš robiť logický súčin
{
value = value - "0xFFFFFFFF" + 1 ;
}
}
else if ( l === 1 )
{
value = response [ 0 ] ;
if ( value >= ( 2 * * 15 ) ) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x8000), ak vieš robiť logický súčin
{
value = value - "0xFFFF" + 1 ;
}
}
return Math . round ( value * multiplier * 10 ) / 10 ;
}
checkNullVoltage = ( values ) => {
if ( ! ( values . hasOwnProperty ( "Phase_1_voltage" ) || values . hasOwnProperty ( "Phase_2_voltage" ) || values . hasOwnProperty ( "Phase_3_voltage" ) ) ) return ;
Object . keys ( values ) . map ( singleValue => {
if ( [ "Phase_1_voltage" , "Phase_2_voltage" , "Phase_3_voltage" ] . includes ( singleValue ) )
{
let l = singleValue . split ( "_" ) ;
let phase = parseInt ( l [ 1 ] ) ;
// console.log(values[singleValue], tbName);
if ( values [ singleValue ] == 0 )
{
FLOW . OMS _no _voltage . add ( phase ) ;
2024-09-09 12:32:34 +02:00
sendNotification ( "modbus_reader: checkNullVoltage" , tbName , "no_voltage_on_phase" , { phase : phase } , "" , SEND _TO . tb , instance , "voltage" + phase ) ;
2024-04-13 20:29:31 +02:00
// console.log('no voltage')
}
else
{
FLOW . OMS _no _voltage . delete ( phase ) ;
// console.log('voltage detected')
2024-09-09 12:32:34 +02:00
sendNotification ( "modbus_reader: checkNullVoltage" , tbName , "voltage_on_phase_restored" , { phase : phase } , "" , SEND _TO . tb , instance , "voltage" + phase ) ;
2024-04-13 20:29:31 +02:00
}
}
} )
}
2024-09-09 12:32:34 +02:00
/ * *
* function sends notification to slack and to tb , if EM total _power value changes more than 500. This should show , that RVO lamps has been switched on or off
* /
lampSwitchNotification = ( values ) => {
if ( ! values . hasOwnProperty ( "total_power" ) ) return ;
const actualTotalPower = values . total _power ;
if ( actualTotalPower > 600 && this . onNotificationSent == false )
{
sendNotification ( "modbus_reader: lampSwitchNotification" , tbName , "lamps_have_turned_on" , { } , "" , SEND _TO . tb , instance ) ;
this . onNotificationSent = true ;
this . offNotificationSent = false ;
}
else if ( actualTotalPower <= 600 && this . offNotificationSent == false )
{
sendNotification ( "modbus_reader: lampSwitchNotification" , tbName , "lamps_have_turned_off" , { } , "" , SEND _TO . tb , instance ) ;
this . onNotificationSent = false ;
this . offNotificationSent = true ;
}
}
2024-04-13 20:29:31 +02:00
}
2024-05-21 15:49:42 +02:00
const isObjectEmpty = ( objectName ) => {
return Object . keys ( objectName ) . length === 0 && objectName . constructor === Object ;
}
2024-04-13 20:29:31 +02:00
setTimeout ( ( ) => {
2024-09-09 12:32:34 +02:00
mainSocket = new SocketWithClients ( ) ;
2024-04-13 20:29:31 +02:00
tbName = FLOW . OMS _rvo _tbname ;
2024-09-09 12:32:34 +02:00
// this notification is to show, that flow (unipi) has been restarted
sendNotification ( "modbus_reader" , tbName , "flow_restart" , { } , "" , SEND _TO . slack , instance ) ;
2024-04-13 20:29:31 +02:00
} , 25000 ) ;
2024-09-09 12:32:34 +02:00
2024-04-13 20:29:31 +02:00
}
2024-05-21 15:49:42 +02:00