110 Commits

Author SHA1 Message Date
a0efe1129b registers changed 2019-07-17 16:15:21 +01:00
7a8a3c661d cmd server adjusted for new register types 2019-07-17 17:12:22 +02:00
122fce519c coil stuff 2019-07-17 16:01:28 +01:00
4200aaf304 coil 2019-07-17 16:47:14 +02:00
0f037b02ea handling of bit reads 2019-07-17 16:26:51 +02:00
ac47ff0ebe fix in converter and json handling 2019-07-17 15:06:28 +01:00
264d1cab14 enable pub and sub at the same holding register 2019-07-17 15:36:45 +02:00
ab91feafd0 fix uint32 converter 2019-07-17 14:17:51 +01:00
2460f570d8 another converter 2019-07-17 14:49:01 +02:00
f6d4218e57 fix in bitwise output 2019-07-17 11:22:50 +01:00
bf3475a796 improve bit output 2019-07-17 12:18:48 +02:00
0bae0f4bb2 fixes in converting code 2019-07-17 10:59:47 +01:00
b9e0fefe17 introduce converter stuff 2019-07-17 11:51:15 +02:00
34ca87f734 write stuff 2019-07-16 17:11:53 +01:00
de01ec20e2 start with writing 2019-07-16 17:18:40 +02:00
6821364273 code beautifying 2019-07-16 14:27:12 +02:00
e297149772 change configuration 2019-07-16 13:21:26 +01:00
6243cad505 remove pickle imports 2019-07-16 14:19:13 +02:00
8fa69d1610 drop pickle file 2019-07-16 14:17:41 +02:00
2fc6fe0830 fix in CmdServer and change file name in config to .json 2019-07-16 13:16:35 +01:00
d97b7469fe jsonifying register file done so far 2019-07-16 14:11:04 +02:00
2665fdd5e3 jsonify test complete, approach seems to work 2019-07-16 12:56:38 +02:00
3e1b9acb86 jsonify test 2019-07-15 21:36:23 +02:00
21555736f6 fixes related to not set subscribeTopic and logger in objects 2019-07-15 15:57:39 +01:00
ab31aea3e0 do not make logger a member of register classes 2019-07-15 15:40:46 +01:00
90cdde8bfe more logging 2019-07-15 15:35:31 +01:00
b7193d8d58 more logging 2019-07-15 15:33:07 +01:00
8c87460632 changes in registers file 2019-07-15 15:28:16 +01:00
454632ec36 debug 2019-07-15 15:11:38 +01:00
d89d705b76 config switch for modbus debugging 2019-07-15 15:24:23 +02:00
9f6f449c5a logging 2019-07-14 00:47:16 +02:00
5a9a6d67f0 fix load registers 2019-07-13 21:39:54 +01:00
21c9bd0eb1 Merge branch 'logging' of gitlab.com:wolutator/modbusmaster into logging 2019-07-14 00:39:04 +02:00
cefebd69c6 log msg added 2019-07-14 00:38:43 +02:00
a251015a33 load registers 2019-07-14 00:37:27 +02:00
c1bb481cac update pickle file 2019-07-14 00:24:03 +02:00
c3cd27261d drop appName 2019-07-14 00:11:03 +02:00
1cf6378ca0 fix logging stuff 2019-07-13 21:27:59 +01:00
34766a0d32 logging and updata registers file 2019-07-13 21:08:07 +01:00
c6552317db now it's getting interesting, will I see this in the log file 2019-07-13 23:51:30 +02:00
870bbcb66b start logging 2019-07-13 23:47:05 +02:00
2c4b42850e introduce inter comm delay, solves most error situations 2019-07-11 12:06:05 +01:00
b48f56260a comment obsolete import 2019-07-10 13:13:13 +02:00
048dceeb02 remove obsolete import 2019-07-10 13:11:44 +02:00
3b61437da7 fix 2019-07-10 13:07:04 +02:00
782a4a296b do the whole RS485 DE handling in Python 2019-07-10 13:05:03 +02:00
7c6ebd8d0c remove obsolete code 2019-07-10 12:42:33 +02:00
b63e513200 wiringpi instead of wiringpi2 2019-07-10 12:38:47 +02:00
57c2c7251d stats 2019-07-10 12:35:02 +02:00
b179b4de76 stats 2019-07-10 12:34:03 +02:00
8540bd6da5 stats 2019-07-10 12:30:52 +02:00
338d289fa6 more stats 2019-07-10 12:30:15 +02:00
5982486940 stats 2019-07-10 12:26:47 +02:00
3c6c420178 better reset stats handling 2019-07-10 12:21:48 +02:00
edeb4ea0f0 reset stats in separate function 2019-07-10 12:19:55 +02:00
06733b0507 reset enqueued flag when loading registers 2019-07-10 12:18:21 +02:00
fc0f7b0a1a forgotten reset of processCount 2019-07-10 12:16:18 +02:00
b1e0e700d3 debug 2019-07-10 12:15:40 +02:00
39a1b18234 fix 2019-07-10 12:12:50 +02:00
46e40500f5 debug 2019-07-10 12:11:22 +02:00
a1acf04dbe error count and error signal 2019-07-10 12:08:31 +02:00
87e2e65ce2 enable debugging 2019-07-10 10:24:17 +01:00
22b8ee6404 changes 2019-07-08 19:50:37 +01:00
9bdd889bc4 fix naming in str 2019-07-08 17:12:35 +02:00
e9e2e41491 add change command 2019-07-08 17:07:14 +02:00
55344b158b whatever 2019-07-08 16:33:37 +02:00
80a8304986 refactor 2019-07-08 16:33:21 +02:00
6229989dd6 fix 2019-07-08 15:28:13 +01:00
e1750e5387 updateonly handling 2019-07-08 16:25:13 +02:00
99238720c9 change registers 2019-07-08 15:15:16 +01:00
3024cadb5d discrete input 2019-07-08 16:07:26 +02:00
1a1ba21c45 remove output 2019-07-08 15:53:23 +02:00
7c8663d539 fixes 2019-07-08 14:51:36 +01:00
37aa84d0f5 actual publish something 2019-07-08 15:48:35 +02:00
58467d9c9d changes 2019-07-08 14:36:47 +01:00
12f83d21ff fix 2019-07-08 15:30:47 +02:00
93b3333356 add actual modbus communication 2019-07-08 15:28:46 +02:00
303f4b50f1 Improvements at cmd interface 2019-07-08 14:27:53 +02:00
25bb774a5d fix cmdServer 2019-07-08 12:28:55 +02:00
c6a340746a Fix import 2019-07-08 12:26:03 +02:00
f7290b3ef2 import AbstractNotificationReceiver from NotificationForwareder 2019-07-08 12:24:36 +02:00
04c1d777e4 prepare application 2019-07-08 12:12:13 +02:00
756ba2175d changes for xy-md02 2019-07-08 09:28:33 +01:00
75ddb6069a discrete input 2019-07-07 02:35:40 +02:00
55f875e27e Merge branch 'master' of gitlab.com:wolutator/modbusmaster 2019-07-06 23:27:58 +01:00
761a1b35e9 dam112 changes 2019-07-06 23:27:26 +01:00
851cfd76d8 additional check in add command 2019-06-27 12:17:17 +02:00
8d6d8e5901 add command implemented 2019-06-27 12:13:27 +02:00
54d33007fc priority handling fixed, list and del command in admin intf 2019-06-26 16:46:41 +02:00
a2a5a924bd mqtt handling 2019-06-25 17:26:23 +02:00
c30acfabdb still working 2019-06-24 17:15:47 +02:00
44f82937d3 cmd handle working so far 2019-06-24 17:01:10 +02:00
7674aac137 cmd handler 2019-06-24 16:03:57 +02:00
37548cfd53 be more cooperative, delay queue feeder by minimum scan rate in register list 2019-06-24 13:17:09 +02:00
4a090c5a73 priority handling tested 2019-06-22 00:45:28 +02:00
7990567378 mostly pseudo code yet 2019-06-21 18:57:00 +02:00
04be7219c2 adjust schema 2019-06-21 13:20:22 +02:00
146f8df8e8 schema extended 2019-06-20 22:01:28 +02:00
186b1c5adb initial schema 2019-06-20 00:23:53 +02:00
2ba9f83569 loop works 2019-06-19 22:47:06 +01:00
3c582ca833 refactored 2019-06-19 23:06:48 +02:00
3fbcedc7ed timeout 2019-06-19 21:52:15 +01:00
344b68e3e3 Merge branch 'master' of gitlab.com:wolutator/modbusmaster 2019-06-19 21:00:51 +01:00
5e15f1cda6 extend modbus test 2019-06-19 21:00:03 +01:00
eee1db510c indent fixed 2019-06-18 19:08:35 +02:00
d19bc80783 fix 2019-06-18 19:06:59 +02:00
fcefd538d8 last notes 2019-06-18 19:05:27 +02:00
faa8b82236 enabling rs485 not longer required 2019-06-18 18:35:39 +02:00
3fd0ed27b4 new test script and ENV script 2019-06-18 07:27:03 +01:00
bf12960dc4 fix polarity of DE, osci image of working example 2019-06-17 21:30:59 +01:00
31 changed files with 1962 additions and 8 deletions

2
ENV Normal file
View File

@ -0,0 +1,2 @@
export LD_LIBRARY_PATH=/home/pi/modbusmaster/pyserialext/
export PYTHONPATH=/home/pi/modbusmaster/pyserialext/

BIN
docs/modbus-really-ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -9,17 +9,17 @@ const uint8_t DE_PIN = 0;
int init() {
wiringPiSetup();
pinMode(DE_PIN, OUTPUT);
digitalWrite(DE_PIN, HIGH);
digitalWrite(DE_PIN, LOW);
}
ssize_t writec(int fd, char *buf, size_t count) {
digitalWrite(DE_PIN, LOW);
digitalWrite(DE_PIN, HIGH);
ssize_t r = write(fd, buf, count);
uint8_t lsr;
do {
int r = ioctl(fd, TIOCSERGETLSR, &lsr);
} while (!(lsr & TIOCSER_TEMT));
digitalWrite(DE_PIN, HIGH);
digitalWrite(DE_PIN, LOW);
return r;
}

123
readme.md
View File

@ -1,5 +1,8 @@
# modbusmaster
![The whole setup](./docs/2019-06-14_22-06-52-1.jpg)
## Disable Bluetooth on RPi3
Add at the end of `/boot/config`:
@ -11,6 +14,7 @@ Remove mentions of `serial0` from `/boot/cmdline`.
## Enable rs485 mode
<del>
Use the submodule rpirtscts to enable to alternate functions of the related
pins at the RPi MCU. It is submoduled here, can be found directly at https://github.com/mholling/rpirtscts
@ -23,6 +27,10 @@ This needs to be done at every boot.
Kudos to danjperron, cmp. https://www.raspberrypi.org/forums/viewtopic.php?f=98&t=224533&hilit=rs+485#p1383709
(Note, please: This software is under the GPL 3.0 license. However, I do not derive from this software, I use it in an unchanged way. It is not integrated into my sources, it just needs to be called once the RPi has booted.)
</del>
The approach to enable the transmitter has been changed due to timing issues with disabling the transmitter after the TX phase when talking to Modbus devices (see below), so this step is not longer required.
## Pinout
@ -38,6 +46,10 @@ TX is at GPIO14, RX is at GPIO15 and RTS (control line for transmitter enable) i
## Python snippet to test
### Pure RS485 test
#### Test 1: Transmit only
(See also `./snippets/test1.py`.)
import serial.rs485
@ -52,4 +64,115 @@ Find an signal screenshot here:
Channel 1 in yellow has the TX line (GPIO14), channel 3 in purple has the RTS (GPIO17, transmitter enable line).
#### Test 2: Transmit and receive
(See also `./snippets/test2.py`.)
Here in contrast to the last script first an octet is received and then echoed.
import serial.rs485
ser=serial.rs485.RS485(port='/dev/ttyAMA0',baudrate=2400)
ser.rs485_mode = serial.rs485.RS485Settings(False,True)
ser.write('a test'.encode('utf-8'))
while True:
c = ser.read(1)
ser.write(c)
print(c, end='')
The signal graph is here:
![Test2 Signals](./docs/osci2.png)
Channel 1 in yellow has the TX line (GPIO14), channel 2 in blue has the RX line and channel 3 in purple has the RTS (GPIO17, transmitter enable line).
It is obvious that the DE (transmitter enable line, RTS controlled by `pyserial` in RS485 mode) is hold active a good while after all data already have been transmitted.
#### Test 3: First Modbus communication
(See also `./snippets/test3.py`.)
from pymodbus.client.sync import ModbusSerialClient
import serial.rs485
ser=serial.rs485.RS485(port='/dev/ttyAMA0',baudrate=1200)
ser.rs485_mode = serial.rs485.RS485Settings(rts_level_for_tx=False,
rts_level_for_rx=True,
delay_before_tx=0.005,
delay_before_rx=-0.0)
client = ModbusSerialClient(method='rtu')
client.socket = ser
client.connect()
result = client.read_holding_registers(address=0x2000, count=2, unit=1)
print(result)
print(result.registers)
client.close()
Here, the port is initialized in RS485 mode and a device is queried.
Sometimes this works:
![Modbus comm ok](./docs/modbus-ok.png)
But sometimes is does not work:
![Modbus comm not ok](./docs/modbus-not-ok.png)
The long hold time of about 18ms of the DE (transmitter enable, RTS line) becomes a problem, the response of the device already starts when the transmitter of the master is still enabled and thus the receiver of the master is still disabled.
A couple of experiments with deriving from the `RS485` class of `pyserial` and moving the time critical code (disabling the transmitter after the transmit) into C code failed. It wasn't faster at all. It became obvious that the system call `tcdrain`, which waits for all octets in the buffer to be transmitted returns very late.
Finally, the solution was to get away from the RS485 mode in `pyserial` and instead use the line status register of the UART via a system call to see whether the transmit register is empty and switch the DE line of the transmitter not longer with the RTS functionality but directly using `wiringPi`.
Derived class from `RS485` in `pyserial` (maybe this can be switched to the regular `Serial` class ...)
class RS485Ext(serial.rs485.RS485):
def __init__(self, *args, **kwargs):
super(RS485Ext, self).__init__(*args, **kwargs)
self.writec = ctypes.cdll.LoadLibrary('writec.so')
r = self.writec.init()
def write(self, b):
d = serial.serialutil.to_bytes(b)
r = self.writec.writec(self.fileno(), d, len(d))
return r
C code of the function `writec` in the library `writec`:
ssize_t writec(int fd, char *buf, size_t count) {
digitalWrite(DE_PIN, HIGH);
ssize_t r = write(fd, buf, count);
uint8_t lsr;
do {
int r = ioctl(fd, TIOCSERGETLSR, &lsr);
} while (!(lsr & TIOCSER_TEMT));
digitalWrite(DE_PIN, LOW);
return r;
}
This change brought the break through:
![Delta between TX end and DE disable](./docs/rs485_should_be_ok_now.JPG)
The delta between TX end and DE disable is now only 2.2ms (instead of nearly 20ms before).
And finally Modbus communication works a lot more reliable:
![Modbus comm nearly really ok](./docs/modbus-really-ok.png)
However, still a lot of errors in the Modbus communication, certainly because of the dirty RX signal (blue). Furthermore it appears that the communication fails completely as soon as the termination resistor of 120 Ohms was placed.
A hint from the application AN-960 from Analog Devices (https://www.analog.com/media/en/technical-documentation/application-notes/AN-960.pdf) helped here. In chapter "Fail-Safe Biasing" it is explained that, when no transmitter is active at all the lines are completely floating and thus the RX signal behind the transceiver becomes dirty, as in the image above.
Pulling the A line to `Vcc` and the B line to `Gnd` using 1.5kOhm resistors, as proposed, solved this problem. And now also the termination resistor worked as expected.
![Now it really works](./docs/nice-signals-with-fail-safe-resistors.png)

1
rpirtscts Submodule

Submodule rpirtscts added at 612b065e38

212
schema/conf-prov.sql Normal file
View File

@ -0,0 +1,212 @@
-- Configuration and Provisioning Schema
DROP TABLE tReadDatapoint;
CREATE TABLE tReadDatapoint (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
unit INTEGER NOT NULL,
address INTEGER NOT NULL,
count INTEGER NOT NULL,
converter VARCHAR(10) NOT NULL,
label VARCHAR(128) NOT NULL,
scanRate TIME(3) DEFAULT '00:00:01.000',
topic VARCHAR(256) NOT NULL,
lastContact TIMESTAMP(3) NOT NULL DEFAULT '2000-01-01 00:00:01.000',
lastError VARCHAR(512),
lastValue VARCHAR(512),
backoff TIME(3) DEFAULT '00:00:00.000',
available BOOLEAN DEFAULT TRUE,
retries INTEGER NOT NULL DEFAULT 0,
giveUpCount INTEGER NOT NULL DEFAULT 0,
active BOOLEAN NOT NULL DEFAULT TRUE,
CONSTRAINT uniqueReadDatapoint UNIQUE (unit, address, count, label)
);
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(4, 0x2000, 2, 'F', '(ERR) Unavailable device', 'IoT/ModbusMaster1/UnavailableDevice', '00:00:01.000');
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(1, 0x2000, 4, 'F', '(ERR) Wrong register size', 'IoT/ModbusMaster1/WrongRegisterSize', '00:05:00.000');
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(1, 0x2000, 2, 'F', 'Voltage', 'IoT/ModbusMaster1/Voltage', '00:05:00.000');
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(1, 0x2020, 2, 'F', 'Frequency', 'IoT/ModbusMaster1/Frequency', '00:05:00.000');
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(1, 0x2060, 2, 'F', 'Current', 'IoT/ModbusMaster1/Current', '00:05:00.000');
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(3, 0x0004, 2, 'RF', 'Resistance Channel 1', 'IoT/ModbusMaster1/Channel1/Resistance', '00:00:01.000');
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(3, 0x000C, 2, 'RF', 'Temperature Channel 1', 'IoT/ModbusMaster1/Channel1/Temperature', '00:00:01.000');
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(3, 0x0014, 2, 'RF', 'Resistance Channel 2', 'IoT/ModbusMaster1/Channel2/Resistance', '00:00:01.000');
INSERT INTO tReadDatapoint (unit, address, count, converter, label, topic, scanRate)
VALUES(3, 0x001C, 2, 'RF', 'Temperature Channel 2', 'IoT/ModbusMaster1/Channel2/Temperature', '00:00:01.000');
DROP TABLE tWriteDatapoint;
CREATE TABLE tWriteDatapoint (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
unit INTEGER NOT NULL,
address INTEGER NOT NULL,
count INTEGER NOT NULL,
converter VARCHAR(10) NOT NULL,
label VARCHAR(128) NOT NULL,
topic VARCHAR(256) NOT NULL,
lastContact TIMESTAMP(3) NOT NULL DEFAULT '2000-01-01 00:00:01.000',
lastError VARCHAR(512),
value VARCHAR(512),
retries INTEGER NOT NULL DEFAULT 0,
active BOOLEAN NOT NULL DEFAULT TRUE,
CONSTRAINT uniqueWriteDatapoint UNIQUE (unit, address, count, label)
);
INSERT INTO tWriteDatapoint (unit, address, count, converter, label, topic, active)
VALUES(5, 0x0000, 1, 'B', 'Relay 1', 'IoT/ModbusMaster1/Relay1', FALSE);
CREATE OR REPLACE VIEW vReadDatapointsToBeHandled AS
SELECT id, unit, address, count, converter
FROM tReadDatapoint
WHERE available AND
active AND
ADDTIME(lastContact, ADDTIME(scanRate, backoff)) < NOW(3)
ORDER BY scanRate;
CREATE OR REPLACE VIEW vWriteDatapintsToBeHandled AS
SELECT id, unit, address, count, converter, value
FROM tWriteDatapoint
WHERE active;
DROP TABLE tReadNotification;
CREATE TABLE tReadNotification (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
readDatapointId INTEGER NOT NULL REFERENCES tReadDatapoint(id),
notificationType VARCHAR(1),
CONSTRAINT checkNotificationType CHECK (notificationtype IN ('V', 'F', 'R')) -- value, failure, return
);
DROP TABLE tWrittenNotification;
CREATE TABLE tWrittenNotification (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
writeDatapointId INTEGER NOT NULL REFERENCES tWriteDatapoint(id),
notificationType VARCHAR(1),
CONSTRAINT checkNotificationType CHECK (notificationtype IN ('S', 'F')) -- success, failure
);
DELIMITER $$
CREATE OR REPLACE PROCEDURE prWriteFeedback (IN p_id INTEGER, IN p_lastError VARCHAR(512))
MODIFIES SQL DATA
BEGIN
DECLARE v_retries INTEGER;
DECLARE v_active BOOLEAN;
IF p_lastError = '' OR p_lastError IS NULL THEN
UPDATE tWriteDatapoint
SET lastError = NULL,
lastContact = NOW(3),
retries = 0,
active = FALSE
WHERE id = p_id;
INSERT INTO tWrittenNotification (writeDatapointId, notificationType) VALUES (p_id, 'S');
ELSE
SELECT retries
INTO v_retries
FROM tWriteDatapoint
WHERE id = p_id;
SET v_retries := v_retries + 1;
IF v_retries >= 5 THEN
SET v_retries := 0;
SET v_active := FALSE;
ELSE
SET v_active := TRUE;
END IF;
UPDATE tWriteDatapoint
SET lastError = p_lastError,
retries = v_retries,
active = v_active
WHERE id = p_id;
IF NOT v_active THEN
INSERT INTO tWrittenNotification (writeDatapointId, notificationType) VALUES(p_id, 'F');
END IF;
END IF;
END; $$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE PROCEDURE prReadFeedback (IN p_id INTEGER, IN p_lastValue VARCHAR(512), IN p_lastError VARCHAR(512))
MODIFIES SQL DATA
BEGIN
DECLARE v_retries INTEGER;
DECLARE v_backoff TIME(3);
DECLARE v_scanRate TIME(3);
DECLARE v_giveUpCount INTEGER;
DECLARE v_available BOOLEAN;
IF p_lastError = '' OR p_lastError IS NULL THEN
UPDATE tReadDatapoint
SET lastError = NULL,
lastContact = NOW(3),
lastValue = p_lastValue,
retries = 0,
backoff = '00:00:00.000',
giveUpCount = 0
WHERE id = p_id;
INSERT INTO tReadNotification (readDatapointId, notificationType) VALUES(p_id, 'V');
ELSE
SELECT retries, backoff, scanRate, giveUpCount
INTO v_retries, v_backoff, v_scanRate, v_giveUpCount
FROM tReadDatapoint
WHERE id = p_id;
SET v_retries := v_retries + 1;
IF v_retries >= 5 THEN
IF v_backoff = '00:00:00.000' THEN
SET v_backoff = v_scanRate;
ELSE
SET v_backoff = ADDTIME(v_backoff, v_backoff);
END IF;
SET v_retries := 0;
SET v_giveUpCount := v_giveUpCount + 1;
SET v_available := TRUE;
END IF;
IF v_giveUpCount = 10 THEN
SET v_available := FALSE;
SET v_giveUpCount := 0;
SET v_backoff := '00:00:00.000';
END IF;
UPDATE tReadDatapoint
SET lastError = p_lastError,
retries = v_retries,
backoff = v_backoff,
giveUpCount = v_giveUpCount,
available = v_available
WHERE id = p_id;
IF NOT v_available THEN
INSERT INTO tReadNotification (readDatapointId, notificationType) VALUES(p_id, 'F');
END IF;
END IF;
END; $$
DELIMITER ;

View File

@ -1,12 +1,99 @@
from pymodbus.client.sync import ModbusSerialClient
from pymodbus.pdu import ExceptionResponse
import RS485Ext
import struct
import time
ser=RS485Ext.RS485Ext(port='/dev/ttyAMA0', baudrate=1200)
def registersToIeeeFloat(i):
return struct.unpack('f', bytes(
[((x & 0xff00) >> 8) if y else (x & 0x00ff)
for x in i[::-1]
for y in [False, True]
]
)
)[0]
def registersToIeeeFloatReverse(i):
return struct.unpack('f', bytes(
[((x & 0xff00) >> 8) if y else (x & 0x00ff)
for x in i
for y in [False, True]
]
)
)[0]
class ModbusException(Exception):
def __init__(self, resp):
self.msg = str(result)
def __str__(self):
return self.msg
ser=RS485Ext.RS485Ext(port='/dev/ttyAMA0', baudrate=1200, stopbits=1)
client = ModbusSerialClient(method='rtu')
client.socket = ser
client.connect()
delay = 0.05
try:
# BG-Tech, Voltage
result = client.read_holding_registers(address=0x2000, count=2, unit=1)
print(result)
print(result.registers)
if type(result) == ExceptionResponse:
raise ModbusException(result)
print("Voltage: {:.2f}".format(registersToIeeeFloat(result.registers)))
time.sleep(delay)
# BG-Tech, Frequency
result = client.read_holding_registers(address=0x2020, count=2, unit=1)
if type(result) == ExceptionResponse:
raise ModbusException(result)
print("Frequency: {:.2f}".format(registersToIeeeFloat(result.registers)))
time.sleep(delay)
# BG-Tech, Current
result = client.read_holding_registers(address=0x2060, count=2, unit=1)
if type(result) == ExceptionResponse:
raise ModbusException(result)
print("Current: {:.2f}".format(registersToIeeeFloat(result.registers)))
time.sleep(delay)
# Hottis Thermometer, Resistance Channel 1
result = client.read_holding_registers(address=0x0004, count=2, unit=3)
if type(result) == ExceptionResponse:
raise ModbusException(result)
print("Resistance Channel 1: {:.2f}".format(registersToIeeeFloatReverse(result.registers)))
time.sleep(delay)
# Hottis Thermometer, Temperature Channel 1
result = client.read_holding_registers(address=0x000c, count=2, unit=3)
if type(result) == ExceptionResponse:
raise ModbusException(result)
print("Temperature Channel 1: {:.2f}".format(registersToIeeeFloatReverse(result.registers)))
time.sleep(delay)
# Hottis Thermometer, Resistance Channel 2
result = client.read_holding_registers(address=0x0014, count=2, unit=3)
if type(result) == ExceptionResponse:
raise ModbusException(result)
print("Resistance Channel 2: {:.2f}".format(registersToIeeeFloatReverse(result.registers)))
time.sleep(delay)
# Hottis Thermometer, Temperature Channel 2
result = client.read_holding_registers(address=0x001c, count=2, unit=3)
if type(result) == ExceptionResponse:
raise ModbusException(result)
print("Temperature Channel 2: {:.2f}".format(registersToIeeeFloatReverse(result.registers)))
except ModbusException as e:
print("ERROR: %s" % e)
finally:
client.close()

21
snippets/test6.py Normal file
View File

@ -0,0 +1,21 @@
from pymodbus.client.sync import ModbusSerialClient
import RS485Ext
import time
ser=RS485Ext.RS485Ext(port='/dev/ttyAMA0', baudrate=9600, stopbits=1)
client = ModbusSerialClient(method='rtu')
client.socket = ser
client.connect()
while True:
try:
result = client.read_holding_registers(address=0x9c42, count=1, unit=1)
print(result)
print(result.registers)
except Exception as e:
print("ERROR: %s" % str(e))
time.sleep(2)
client.close()

23
snippets/test6b.py Normal file
View File

@ -0,0 +1,23 @@
from pymodbus.client.sync import ModbusSerialClient
import RS485Ext
import time
ser=RS485Ext.RS485Ext(port='/dev/ttyAMA0', baudrate=9600, stopbits=1)
client = ModbusSerialClient(method='rtu')
client.socket = ser
client.connect()
try:
result = client.read_holding_registers(address=0x9c43, count=1, unit=4)
# result = client.read_holding_registers(address=0x0102, count=1, unit=5)
# result = client.read_input_registers(address=0x0002, count=1, unit=5)
# result = client.read_discrete_inputs(address=0x0000, count=1, unit=4)
print(result)
print(result.registers)
# print(result.bits)
except Exception as e:
print("ERROR: %s" % str(e))
client.close()

19
snippets/test6c.py Normal file
View File

@ -0,0 +1,19 @@
from pymodbus.client.sync import ModbusSerialClient
import RS485Ext
import time
ser=RS485Ext.RS485Ext(port='/dev/ttyAMA0', baudrate=9600, stopbits=1)
client = ModbusSerialClient(method='rtu')
client.socket = ser
client.connect()
try:
result = client.write_register(address=0x9c48, unit=4, value=0x000F)
# result = client.write_coil(address=0x0000, unit=4, value=1)
print(result)
except Exception as e:
print("ERROR: %s" % str(e))
client.close()

25
snippets/test6d.py Normal file
View File

@ -0,0 +1,25 @@
from pymodbus.client.sync import ModbusSerialClient
import RS485Ext
import time
ser=RS485Ext.RS485Ext(port='/dev/ttyAMA0', baudrate=1200, stopbits=1)
client = ModbusSerialClient(method='rtu')
client.socket = ser
client.connect()
v = 0
while True:
if v == 0:
v = 1
else:
v = 0
try:
result = client.write_coil(address=0x0000, unit=4, value=v)
print(result)
except Exception as e:
print("ERROR: %s" % str(e))
time.sleep(0.5)
client.close()

21
snippets/test6e.py Normal file
View File

@ -0,0 +1,21 @@
from pymodbus.client.sync import ModbusSerialClient
import RS485Ext
import time
ser=RS485Ext.RS485Ext(port='/dev/ttyAMA0', baudrate=1200, stopbits=1)
client = ModbusSerialClient(method='rtu')
client.socket = ser
client.connect()
while True:
try:
result = client.read_discrete_inputs(address=0x0000, count=1, unit=4)
print(result)
print(result.bits)
except Exception as e:
print("ERROR: %s" % str(e))
time.sleep(0.5)
client.close()

117
snippets/test7.py Normal file
View File

@ -0,0 +1,117 @@
from pymodbus.client.sync import ModbusSerialClient
from pymodbus.pdu import ExceptionResponse
from pymodbus.exceptions import ModbusIOException
import RS485Ext
import struct
import time
def registersToIeeeFloat(i):
return struct.unpack('f', bytes(
[((x & 0xff00) >> 8) if y else (x & 0x00ff)
for x in i[::-1]
for y in [False, True]
]
)
)[0]
def registersToIeeeFloatReverse(i):
return struct.unpack('f', bytes(
[((x & 0xff00) >> 8) if y else (x & 0x00ff)
for x in i
for y in [False, True]
]
)
)[0]
def dataConverter(t, d):
if t == 'F':
return registersToIeeeFloat(d)
elif t == 'RF':
return registersToIeeeFloatReverse(d)
else:
raise Exception("Converter '{0}' is not supported".format(t))
class ModbusException(Exception):
def __init__(self, resp):
self.msg = str(result)
def __str__(self):
return self.msg
class ModbusRequestDefinition(object):
def __init__(self, kind, unit, address, count, converter, label):
self.kind = kind
self.unit = unit
self.address = address
self.count = count
self.converter = converter
self.label = label
reqs = [
# ModbusRequestDefinition(4, 0x2000, 2, 'F', '(ERR) Unavailable device'),
# ModbusRequestDefinition(1, 0x2000, 4, 'F', '(ERR) Wrong register size'),
# ModbusRequestDefinition(1, 0x2000, 2, 'F', 'Voltage'),
# ModbusRequestDefinition(1, 0x2020, 2, 'F', 'Frequency'),
# ModbusRequestDefinition(1, 0x2060, 2, 'F', 'Current'),
# ModbusRequestDefinition('H', 3, 0x0004, 2, 'RF', 'Resistance Channel 1'),
# ModbusRequestDefinition('H', 3, 0x000C, 2, 'RF', 'Temperature Channel 1'),
# ModbusRequestDefinition('H', 3, 0x0014, 2, 'RF', 'Resistance Channel 2'),
# ModbusRequestDefinition('H', 3, 0x001C, 2, 'RF', 'Temperature Channel 2'),
ModbusRequestDefinition('D', 4, 0x0000, 1, '', 'Discrete Input'),
ModbusRequestDefinition('I', 5, 0x0001, 1, '', 'Temperature'),
ModbusRequestDefinition('I', 5, 0x0002, 1, '', 'Humidity'),
]
def getSerial():
return RS485Ext.RS485Ext(port='/dev/ttyAMA0', baudrate=9600, stopbits=1,
timeout=1)
client = ModbusSerialClient(method='rtu')
client.socket = getSerial()
client.connect()
delay = 0.05
period = 0.5
while True:
for req in reqs:
try:
time.sleep(delay)
if req.kind == 'H':
# print("Trying to read: {0} {1} {2}".format(req.address, req.count, req.unit))
result = client.read_holding_registers(address=req.address,
count=req.count,
unit=req.unit)
if type(result) in [ExceptionResponse, ModbusIOException]:
raise ModbusException(result)
print("{0}: {1:.2f}".format(req.label,
dataConverter(req.converter,
result.registers)))
elif req.kind == 'D':
result = client.read_discrete_inputs(address=req.address,
count=req.count,
unit=req.unit)
if type(result) in [ExceptionResponse, ModbusIOException]:
raise ModbusException(result)
print("{0}: {1!s}".format(req.label, result.bits))
elif req.kind == 'I':
result = client.read_input_registers(address=req.address,
count=req.count,
unit=req.unit)
if type(result) in [ExceptionResponse, ModbusIOException]:
raise ModbusException(result)
print("{0}: {1}".format(req.label, result.registers))
except ModbusException as e:
print("ERROR when querying '{0}': {1!s}".format(req.label, e))
if client.socket is None:
print("renew socket")
client.socket = getSerial()
print("-------------")
time.sleep(period)
client.close()

105
snippets/test8.py Normal file
View File

@ -0,0 +1,105 @@
import unittest
import json
class A(object):
def __init__(self, arg1=None, arg2=None):
self.arg1 = arg1
self.arg2 = arg2
self.arg3 = self.arg1 + self.arg2
def __str__(self):
return "A: {0!s} {1!s} {2!s}".format(self.arg1, self.arg2, self.arg3)
def jsonify(self):
return {'type':self.__class__.__name__, 'args': {'arg1':self.arg1, 'arg2':self.arg2}}
class MyEncoder(json.JSONEncoder):
def default(self, o):
try:
return o.jsonify()
except TypeError or AttributeError:
return super().default(o)
def objectFactory(j):
klass = eval(j['type'])
o = klass(**j['args'])
return o
def MyDecoder(j):
if type(j) == dict and 'type' in j:
return objectFactory(j)
else:
return j
class Tests(unittest.TestCase):
def test_a1(self):
a1 = A(1, 2)
self.assertEqual(a1.arg1, 1)
self.assertEqual(a1.arg2, 2)
self.assertEqual(a1.arg3, 3)
def test_a2(self):
a2 = A(**{'arg1':2, 'arg2':4})
self.assertEqual(a2.arg1, 2)
self.assertEqual(a2.arg2, 4)
self.assertEqual(a2.arg3, 6)
def test_a3(self):
j = '{ "type": "A", "args": { "arg1": 3, "arg2": 5 } }'
jj = json.loads(j)
klass = eval(jj['type'])
self.assertEqual(A, klass)
a3 = klass(**jj['args'])
self.assertEqual(a3.arg1, 3)
self.assertEqual(a3.arg2, 5)
self.assertEqual(a3.arg3, 8)
def test_a4(self):
j = '{ "type": "A", "args": { "arg1": 3, "arg2": 5 } }'
jj = json.loads(j)
klass = eval(jj['type'])
self.assertEqual(A, klass)
a3 = klass(**jj['args'])
self.assertEqual(a3.arg1, 3)
self.assertEqual(a3.arg2, 5)
self.assertEqual(a3.arg3, 8)
jjjj = json.dumps(a3, cls=MyEncoder)
jjj = json.loads(jjjj)
klass = eval(jjj['type'])
self.assertEqual(A, klass)
a3 = klass(**jjj['args'])
self.assertEqual(a3.arg1, 3)
self.assertEqual(a3.arg2, 5)
self.assertEqual(a3.arg3, 8)
def test_a5(self):
jList = []
jList.append(objectFactory({'type':'A', 'args': {'arg1':1, 'arg2':2}}))
jList.append(objectFactory({'type':'A', 'args': {'arg1':2, 'arg2':3}}))
jList.append(objectFactory({'type':'A', 'args': {'arg1':3, 'arg2':4}}))
js = json.dumps(jList, cls=MyEncoder, sort_keys=True, indent=4)
print(js)
jResultList = json.loads(js, object_hook=MyDecoder)
self.assertEqual(jResultList[0].arg1, 1)
self.assertEqual(jResultList[0].arg2, 2)
self.assertEqual(jResultList[0].arg3, 3)
self.assertEqual(jResultList[1].arg1, 2)
self.assertEqual(jResultList[1].arg2, 3)
self.assertEqual(jResultList[1].arg3, 5)
self.assertEqual(jResultList[2].arg1, 3)
self.assertEqual(jResultList[2].arg2, 4)
self.assertEqual(jResultList[2].arg3, 7)
if __name__ == '__main__':
unittest.main()

14
snippets/test9.py Normal file
View File

@ -0,0 +1,14 @@
class A(object):
def __init__(self):
self.a = 1
def x(self):
return self.a
class B(A):
def __init__(self):
self.a = 2
def x(self):
return self.a
def y(self):
return super().x()

392
src/CmdServer.py Normal file
View File

@ -0,0 +1,392 @@
import threading
import socketserver
import cmd
import re
import io
import datetime
import RegisterDatapoint
import logging
class CmdInterpreterException(ValueError): pass
def parseIntArbitraryBase(s):
i = 0
if s.startswith('0x'):
i = int(s, 16)
elif s.startswith('0b'):
i = int(s, 2)
else:
i = int(s, 10)
return i
class CmdInterpreter(cmd.Cmd):
def __init__(self, infile, outfile, config, notifier, registers):
super().__init__(stdin=infile, stdout=outfile)
self.use_rawinput = False
self.config = config
self.notifier = notifier
self.registers = registers
self.prompt = "test8> "
self.intro = "test8 admin interface"
self.splitterRe = re.compile('\s+')
self.logger = logging.getLogger('CmdInterpreter')
def __print(self, text):
self.stdout.write(text)
def __println(self, text):
self.stdout.write(text)
self.stdout.write("\n\r")
def do_notify(self, arg):
self.notifier.notify()
def help_notify(self):
self.__println("Notifies threads using the list of datapoints about changes in this list.")
self.__println("Call after modifications on the list.")
def do_quit(self, arg):
self.__println("Bye!")
return True
def do_add_hr(self, arg):
try:
(label, unit, address, count, scanrate, readTopic, writeTopic, feedbackTopic, converter) = self.splitterRe.split(arg)
self.__println("Label: {0}".format(label))
self.__println("Unit: {0}".format(unit))
self.__println("Address: {0}".format(address))
self.__println("Count: {0}".format(count))
self.__println("ScanRate: {0}".format(scanrate))
self.__println("ReadTopic: {0}".format(readTopic))
self.__println("WriteTopic: {0}".format(writeTopic))
self.__println("FeedbackTopic: {0}".format(feedbackTopic))
self.__println("Converter: {0}".format(converter))
if readTopic == 'None':
readTopic = None
if writeTopic == 'None':
writeTopic = None
if feedbackTopic == 'None':
feedbackTopic = None
if converter == 'None':
converter = None
unit = parseIntArbitraryBase(unit)
address = parseIntArbitraryBase(address)
count = parseIntArbitraryBase(count)
scanrate = float(scanrate)
r = RegisterDatapoint.HoldingRegisterDatapoint(label, unit, address, count, datetime.timedelta(seconds=scanrate), readTopic, writeTopic, feedbackTopic, converter)
self.registers.append(r)
except ValueError as e:
self.__println("ERROR: {0!s}, {1!s}".format(e.__class__.__name__, e))
def help_add_hr(self):
# HoldingRegisterDatapoint('Voltage', 1, 0x2000, 2, datetime.timedelta(seconds=10), 'Pub/Voltage', None, None),
self.__println("Usage: add_hr <Label> <Unit> <Address> <Count> <ScanRate>")
self.__println(" <ReadTopic> <WriteTopic> <FeedbackTopic>")
self.__println(" <Converter>")
self.__println("Adds a holding register")
self.__println("DO NOT FORGET TO SAVE AFTERWARDS!")
self.__println("---------------------------------------------------------------------")
self.__println("<Label> Descriptive label")
self.__println("<Unit> Modbus address of the device")
self.__println("<Address> Register address within the device")
self.__println("<Count> Count of registers to be read or write in words")
self.__println("<ScanRate> Scanrate in seconds (float), for write datapoints")
self.__println(" set to zero (0)")
self.__println("<ReadTopic> Topic to publish read data")
self.__println("<WriteTopic> Topic to be subscribe to receive data to be")
self.__println(" written")
self.__println("<FeedbackTopic> Topic to publish feedback after a write process,")
self.__println("<Converter> Converter for data")
def do_add_coil(self, arg):
try:
(label, unit, address, scanrate, readTopic, writeTopic, feedbackTopic) = self.splitterRe.split(arg)
self.__println("Label: {0}".format(label))
self.__println("Unit: {0}".format(unit))
self.__println("Address: {0}".format(address))
self.__println("ScanRate: {0}".format(scanrate))
self.__println("ReadTopic: {0}".format(readTopic))
self.__println("WriteTopic: {0}".format(writeTopic))
self.__println("FeedbackTopic: {0}".format(feedbackTopic))
if readTopic == 'None':
readTopic = None
if writeTopic == 'None':
writeTopic = None
if feedbackTopic == 'None':
feedbackTopic = None
unit = parseIntArbitraryBase(unit)
address = parseIntArbitraryBase(address)
scanrate = float(scanrate)
r = RegisterDatapoint.CoilDatapoint(label, unit, address, datetime.timedelta(seconds=scanrate), readTopic, writeTopic, feedbackTopic)
self.registers.append(r)
except ValueError as e:
self.__println("ERROR: {0!s}, {1!s}".format(e.__class__.__name__, e))
def help_add_coil(self):
self.__println("Usage: add_coil <Label> <Unit> <Address> <ScanRate>")
self.__println(" <ReadTopic> <WriteTopic> <FeedbackTopic>")
self.__println("Adds a coil")
self.__println("DO NOT FORGET TO SAVE AFTERWARDS!")
self.__println("---------------------------------------------------------------------")
self.__println("<Label> Descriptive label")
self.__println("<Unit> Modbus address of the device")
self.__println("<Address> Register address within the device")
self.__println("<ScanRate> Scanrate in seconds (float), for write datapoints")
self.__println(" set to zero (0)")
self.__println("<ReadTopic> Topic to publish read data")
self.__println("<WriteTopic> Topic to be subscribe to receive data to be")
self.__println(" written")
self.__println("<FeedbackTopic> Topic to publish feedback after a write process,")
def do_add_ir(self, arg):
try:
(label, unit, address, count, scanrate, updateOnly, readTopic, converter) = self.splitterRe.split(arg)
self.__println("Label: {0}".format(label))
self.__println("Unit: {0}".format(unit))
self.__println("Address: {0}".format(address))
self.__println("Count: {0}".format(count))
self.__println("ScanRate: {0}".format(scanrate))
self.__println("UpdateOnly: {0}".format(updateOnly))
self.__println("ReadTopic: {0}".format(readTopic))
self.__println("Converter: {0}".format(converter))
if readTopic == 'None':
readTopic = None
if converter == 'None':
converter = None
if updateOnly in ['true', 'True', 'yes', 'Yes']:
updateOnly = True
elif updateOnly in ['false', 'False', 'no', 'No']:
updateOnly = False
else:
raise CmdInterpreterException('updateOnly must be true or false, yes or no')
unit = parseIntArbitraryBase(unit)
address = parseIntArbitraryBase(address)
count = parseIntArbitraryBase(count)
scanrate = float(scanrate)
r = RegisterDatapoint.InputRegisterDatapoint(label, unit, address, count, datetime.timedelta(seconds=scanrate), updateOnly, readTopic, converter)
self.registers.append(r)
except ValueError as e:
self.__println("ERROR: {0!s}, {1!s}".format(e.__class__.__name__, e))
def help_add_ir(self):
self.__println("Usage: add_ir <Label> <Unit> <Address> <Count> <ScanRate>")
self.__println(" <UpdateOnly> <ReadTopic> <Converter>")
self.__println("Adds an input register")
self.__println("DO NOT FORGET TO SAVE AFTERWARDS!")
self.__println("---------------------------------------------------------------------")
self.__println("<Label> Descriptive label")
self.__println("<Unit> Modbus address of the device")
self.__println("<Address> Register address within the device")
self.__println("<Count> Count of registers to be read in words")
self.__println("<ScanRate> Scanrate in seconds (float)")
self.__println("<UpdateOnly> Publish only when value has changed")
self.__println("<ReadTopic> Topic to publish read data")
self.__println("<Converter> Converter for data")
def do_add_di(self, arg):
try:
(label, unit, address, count, scanrate, updateOnly, readTopic, bitCount) = self.splitterRe.split(arg)
self.__println("Label: {0}".format(label))
self.__println("Unit: {0}".format(unit))
self.__println("Address: {0}".format(address))
self.__println("Count: {0}".format(count))
self.__println("ScanRate: {0}".format(scanrate))
self.__println("UpdateOnly: {0}".format(updateOnly))
self.__println("ReadTopic: {0}".format(readTopic))
self.__println("BitCount: {0}".format(bitCount))
if readTopic == 'None':
readTopic = None
if updateOnly in ['true', 'True', 'yes', 'Yes']:
updateOnly = True
elif updateOnly in ['false', 'False', 'no', 'No']:
updateOnly = False
else:
raise CmdInterpreterException('updateOnly must be true or false, yes or no')
unit = parseIntArbitraryBase(unit)
address = parseIntArbitraryBase(address)
count = parseIntArbitraryBase(count)
scanrate = float(scanrate)
bitCount = int(bitCount)
r = RegisterDatapoint.DiscreteInputDatapoint(label, unit, address, count, datetime.timedelta(seconds=scanrate), updateOnly, readTopic, None, bitCount)
self.registers.append(r)
except ValueError as e:
self.__println("ERROR: {0!s}, {1!s}".format(e.__class__.__name__, e))
def help_add_di(self):
self.__println("Usage: add_di <Label> <Unit> <Address> <Count> <ScanRate>")
self.__println(" <UpdateOnly> <ReadTopic> <bitCount>")
self.__println("Adds a discrete input")
self.__println("DO NOT FORGET TO SAVE AFTERWARDS!")
self.__println("---------------------------------------------------------------------")
self.__println("<Label> Descriptive label")
self.__println("<Unit> Modbus address of the device")
self.__println("<Address> Register address within the device")
self.__println("<Count> Count of registers to be read in words")
self.__println("<ScanRate> Scanrate in seconds (float)")
self.__println("<UpdateOnly> Publish only when value has changed")
self.__println("<ReadTopic> Topic to publish read data")
self.__println("<BitCount> Number of bit to be considered")
def do_list(self, arg):
for i, r in enumerate(self.registers):
self.__println("#{0}: {1!s}".format(i, r))
def help_list(self):
self.__println("Usage: list")
self.__println("-----------")
self.__println("List the configured datapoints")
def do_reset(self, arg):
for r in self.registers:
r.errorCount = 0
r.processCount = 0
def help_reset(self):
self.__println("Usage: reset")
self.__println("-----------")
self.__println("Resets the statistics of configured datapoints")
def do_stats(self, arg):
for i, r in enumerate(self.registers):
if r.processCount == 0:
ratio = -1
else:
ratio = float(r.errorCount) / float(r.processCount)
self.__println("#{0:2d}: {1:15s} ({2:2d}, {3:5d}), pc: {4:7d}, ec: {5:7d}, q: {6:1.4f}"
.format(i, r.label, r.unit, r.address,
r.processCount, r.errorCount, ratio))
def help_stats(self):
self.__println("Usage: stats")
self.__println("-----------")
self.__println("List the statistics of configured datapoints")
def do_change(self, arg):
(idx, key, typ, value) = self.splitterRe.split(arg)
try:
i = int(idx)
r = self.registers[i]
if typ == 'I':
value = parseIntArbitraryBase(value)
elif typ == 'F':
value = float(value)
elif typ == 'B':
if value in ['true', 'True', 'yes', 'Yes']:
value = True
elif value in ['false', 'False', 'no', 'No']:
value = False
else:
raise CmdInterpreterException('boolean value must be true or false, yes or no')
elif typ == 'S':
# string
pass
elif typ == 'T':
value = datetime.timedelta(seconds=float(value))
elif typ == 'N':
value = None
else:
raise CmdInterpreterException('unknown type specifier, must be I, F, B, S or T')
if key not in r.__dict__:
raise CmdInterpreterException('selected datapoint does not support key')
r.__dict__[key] = value
except ValueError as e:
self.__println("ERROR: {0!s}, {1!s}".format(e.__class__.__name__, e))
def help_change(self):
self.__println("Usage: change <idx> <key> <type> <value>")
self.__println("Changes on attribute of a datapoint")
self.__println("DO NOT FORGET TO SAVE AFTERWARDS!")
self.__println("---------------------------------------------------------------------")
self.__println("<idx> Index, use list command to find")
self.__println("<key> Name of attribute")
self.__println("<type> Type of attribute")
self.__println(" I .. Integer")
self.__println(" F .. Float")
self.__println(" B .. Boolean")
self.__println(" T .. Timedelta, give in seconds")
self.__println(" S .. String")
self.__println(" N .. None (Value must be given but is not")
self.__println(" considered)")
self.__println("<value> New value")
def do_del(self, arg):
try:
i = int(arg)
r = self.registers[i]
self.registers.remove(r)
self.__println("{0!s} removed".format(r))
except ValueError as e:
self.__println("ERROR: {0!s}".format(e))
def help_del(self):
self.__println("Usage: del <idx>")
self.__println("Removes an item from the list of datapoints by its index, see list command.")
self.__println("Be aware: indexes have been changed, rerun list before removing the next item.")
self.__println("DO NOT FORGET TO SAVE AFTERWARDS!")
def do_save(self, arg):
RegisterDatapoint.saveRegisterList(self.registers, self.config.registerFile)
def help_save(self):
self.__println("Usage: save")
self.__println("Saves a modified register list into the register file.")
def do_load(self, arg):
try:
registers = RegisterDatapoint.loadRegisterList(self.config.registerFile)
self.registers = registers
except Exception as e:
self.__println("Unable to load register list: {0!s}".format(e))
def help_load(self):
self.__println("Usage: load")
self.__println("Reload the register file, overwrite all unsaved changes.")
class CmdHandle(socketserver.StreamRequestHandler):
def handle(self):
logger = logging.getLogger('CmdHandle')
cmd = CmdInterpreter(io.TextIOWrapper(self.rfile), io.TextIOWrapper(self.wfile), self.server.userData.config,
self.server.userData.notifier, self.server.userData.registers)
try:
cmd.cmdloop()
logger.info("Cmd handle terminated")
except ConnectionAbortedError as e:
logger.info("Cmd handle externally interrupted")
class MyThreadingTCPServer(socketserver.ThreadingTCPServer):
def __init__(self, host, handler, userData):
super().__init__(host, handler)
self.userData = userData
class MyCmdUserData(object):
def __init__(self, config, notifier, registers):
self.config = config
self.notifier = notifier
self.registers = registers
class CmdServer(threading.Thread):
def __init__(self, config, notifier, registers):
super().__init__()
self.config = config
self.server = MyThreadingTCPServer((config.cmdAddress, config.cmdPort), CmdHandle, MyCmdUserData(config, notifier, registers))
self.daemon = True
def start(self):
self.server.serve_forever()

View File

@ -0,0 +1,59 @@
import threading
import datetime
# import RS485Ext
import RegisterDatapoint
from pymodbus.client.sync import ModbusSerialClient
import wiringpi
import MyRS485
import time
import logging
ERROR_PIN = 29
class CommunicationProcessor(threading.Thread):
def __init__(self, config, queue, pubQueue):
super().__init__()
self.config = config
self.queue = queue
self.pubQueue = pubQueue
wiringpi.wiringPiSetup()
wiringpi.pinMode(ERROR_PIN, wiringpi.OUTPUT)
self.daemon = True
if self.config.modbusDebug:
logging.getLogger('pymodbus').setLevel(logging.DEBUG)
else:
logging.getLogger('pymodbus').setLevel(logging.ERROR)
self.logger = logging.getLogger('CommunicationProcessor')
def __getSerial(self):
# return RS485Ext.RS485Ext(port=self.config.serialPort, baudrate=self.config.serialBaudRate, stopbits=1,
# timeout=1)
return MyRS485.MyRS485(port=self.config.serialPort, baudrate=self.config.serialBaudRate, stopbits=1,
timeout=1)
def run(self):
client = ModbusSerialClient(method='rtu')
client.socket = self.__getSerial()
client.connect()
while True:
r = self.queue.get()
try:
wiringpi.digitalWrite(ERROR_PIN, wiringpi.LOW)
self.logger.debug("Dequeued: {0!s}".format(r))
r.enqueued = False
r.process(client, self.pubQueue)
except RegisterDatapoint.DatapointException as e:
wiringpi.digitalWrite(ERROR_PIN, wiringpi.HIGH)
self.logger.error("ERROR when processing '{0}': {1!s}".format(r.label, e))
if client.socket is None:
self.logger.error("renew socket")
client.socket = self.__getSerial()
finally:
time.sleep(self.config.interCommDelay)

15
src/Config.py Normal file
View File

@ -0,0 +1,15 @@
class Config(object):
def __init__(self):
self.logFile = '/tmp/mbm.log'
self.modbusDebug = False
self.mqttBrokerHost = '172.16.2.16'
self.mqttBrokerPort = 1883
self.mqttLogin = ''
self.mqttPassword = ''
self.cmdAddress = '127.0.0.1'
self.cmdPort = 9999
self.registerFile = 'registers.json'
self.serialPort = '/dev/ttyAMA0'
self.serialBaudRate = 9600
self.interCommDelay = 0.025

18
src/Converters.py Normal file
View File

@ -0,0 +1,18 @@
# in: from Modbus to MQTT, input is a list of 16bit integers, output shall be the desired format
# to be sent in the MQTT message
# out: from MQTT to Modbus, input is the format received from MQTT, output shall be a list of
# 16bit integers to be written to the Modbus slave
from struct import pack, unpack
Converters = {
"dht20TOFloat": {
"in": lambda x : float(x[0]) / 10.0,
"out": None
},
"uint32": {
"in": lambda x : unpack('L', pack('HH', *x))[0],
"out": lambda x : unpack('HH', pack('L', int(x)))
}
}

88
src/MqttProcessor.py Normal file
View File

@ -0,0 +1,88 @@
import threading
import paho.mqtt.client as mqtt
from NotificationForwarder import AbstractNotificationReceiver
import logging
class PublishItem(object):
def __init__(self, topic, payload):
self.topic = topic
self.payload = payload
def mqttOnConnectCallback(client, userdata, flags, rc):
userdata.onConnect()
def mqttOnMessageCallback(client, userdata, message):
userdata.onMessage(message.topic, message.payload)
def mqttOnDisconnectCallback(client, userdata, rc):
userdata.onDisconnect(rc)
class MqttProcessor(threading.Thread, AbstractNotificationReceiver):
def __init__(self, config, registers, queue, pubQueue):
super().__init__()
self.config = config
self.registers = registers
self.queue = queue
self.pubQueue = pubQueue
self.client = mqtt.Client(userdata=self)
self.subscriptions = []
self.topicRegisterMap ={}
self.daemon = True
self.logger = logging.getLogger('MqttProcessor')
def __processUpdatedRegisters(self, force=False):
self.logger.debug("MqttProcessor.__updateSubscriptions")
subscribeTopics = [ r.subscribeTopic for r in self.registers if hasattr(r,'subscribeTopic') and r.subscribeTopic]
self.logger.debug("Topics: {0!s}".format(subscribeTopics))
for subscribeTopic in subscribeTopics:
if (subscribeTopic not in self.subscriptions) or force:
self.logger.debug("Subscribe to {0}".format(subscribeTopic))
self.client.subscribe(subscribeTopic)
self.subscriptions.append(subscribeTopic)
for subscription in self.subscriptions:
if (subscription not in subscribeTopics) and not force:
self.logger.debug("Unsubscribe from {0}".format(subscription))
self.client.unsubscribe(subscription)
self.subscriptions.remove(subscription)
self.topicRegisterMap = { r.subscribeTopic: r for r in self.registers if hasattr(r,'subscribeTopic') and r.subscribeTopic }
def receiveNotification(self, arg):
self.logger.info("MqttProcessor:registersChanged")
self.__processUpdatedRegisters()
def run(self):
self.client.on_message = mqttOnMessageCallback
self.client.on_connect = mqttOnConnectCallback
self.client.on_disconnect = mqttOnDisconnectCallback
if self.config.mqttLogin and self.config.mqttPassword:
self.client.username_pw_set(self.config.mqttLogin, self.config.mqttPassword)
self.client.connect(self.config.mqttBrokerHost, self.config.mqttBrokerPort)
self.client.loop_start()
while True:
pubItem = self.pubQueue.get()
if isinstance(pubItem, PublishItem):
self.client.publish(pubItem.topic, pubItem.payload)
else:
self.logger.error("Invalid object in publish queue")
def onConnect(self):
# print("MqttProcessor.onConnect")
self.__processUpdatedRegisters(force=True)
def onDisconnect(self, rc):
self.logger.error("Disconnected from MQTT broker: {0}".format(rc))
def onMessage(self, topic, payload):
# print("MqttProcessor.onMessage")
r = self.topicRegisterMap[topic]
self.logger.debug("{0}: {1!s} -> {2!s}".format(topic, payload, r))
r.onMessage(payload)
self.queue.put(r)

22
src/MyPriorityQueue.py Normal file
View File

@ -0,0 +1,22 @@
import queue
class MyPriorityQueueItem(object):
def __init__(self, itemWithPriority):
self.itemWithPriority = itemWithPriority
def __lt__(self, other): return self.itemWithPriority.priority < other.itemWithPriority.priority
def __le__(self, other): return self.itemWithPriority.priority <= other.itemWithPriority.priority
def __eq__(self, other): return self.itemWithPriority.priority == other.itemWithPriority.priority
def __ne__(self, other): return self.itemWithPriority.priority != other.itemWithPriority.priority
def __gt__(self, other): return self.itemWithPriority.priority > other.itemWithPriority.priority
def __ge__(self, other): return self.itemWithPriority.priority >= other.itemWithPriority.priority
class MyPriorityQueue(queue.PriorityQueue):
def _put(self, itemWithPriority):
i = MyPriorityQueueItem(itemWithPriority)
super()._put(i)
def _get(self):
i = super()._get()
return i.itemWithPriority

24
src/MyRS485.py Normal file
View File

@ -0,0 +1,24 @@
import serial
import wiringpi
import array
import fcntl
import termios
DE_PIN = 0
class MyRS485(serial.Serial):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
wiringpi.wiringPiSetup()
wiringpi.pinMode(DE_PIN, wiringpi.OUTPUT)
self.buf = array.array('h', [0])
def write(self, b):
wiringpi.digitalWrite(DE_PIN, wiringpi.HIGH)
super().write(b)
while True:
fcntl.ioctl(self.fileno(), termios.TIOCSERGETLSR, self.buf, 1)
if self.buf[0] & termios.TIOCSER_TEMT:
break
wiringpi.digitalWrite(DE_PIN, wiringpi.LOW)

View File

@ -0,0 +1,15 @@
class AbstractNotificationReceiver(object):
def receiveNotification(self, arg):
raise NotImplementedError
class NotificationForwarder(object):
def __init__(self):
self.receivers = []
def register(self, receiver):
self.receivers.append(receiver)
def notify(self, arg=None):
for r in self.receivers:
r.receiveNotification(arg)

291
src/RegisterDatapoint.py Normal file
View File

@ -0,0 +1,291 @@
import datetime
from pymodbus.pdu import ExceptionResponse
from pymodbus.exceptions import ModbusIOException
import MqttProcessor
import logging
import json
import Converters
class DatapointException(Exception): pass
class AbstractModbusDatapoint(object):
def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, converter=None):
self.argList = ['label', 'unit', 'address', 'count', 'scanRate', 'converter']
self.label = label
self.unit = unit
self.address = address
self.count = count
self.converter = converter
if type(scanRate) == float:
self.scanRate = datetime.timedelta(seconds=scanRate)
else:
self.scanRate = scanRate
self.type = 'abstract data point'
self.enqueued = False
self.lastContact = None
self.errorCount = 0
self.processCount = 0
if self.scanRate:
self.priority = 1
else:
self.priority = 0
def __str__(self):
return ("{0}, {1}: unit: {2}, address: {3}, count: {4}, scanRate: {5}, "
"enqueued: {6}, lastContact: {7}, errorCount: {8}, processCount: {9}, "
"converter: {10}"
.format(self.type, self.label, self.unit, self.address, self.count,
self.scanRate, self.enqueued, self.lastContact,
self.errorCount, self.processCount, self.converter))
def jsonify(self):
return {'type':self.__class__.__name__,
'args': { k: getattr(self, k) for k in self.argList }
}
def process(self, client):
raise NotImplementedError
class HoldingRegisterDatapoint(AbstractModbusDatapoint):
def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None,
publishTopic=None, subscribeTopic=None, feedbackTopic=None, converter=None):
super().__init__(label, unit, address, count, scanRate, converter)
self.argList = self.argList + ['publishTopic', 'subscribeTopic', 'feedbackTopic']
self.publishTopic = publishTopic
self.subscribeTopic = subscribeTopic
self.feedbackTopic = feedbackTopic
self.writeRequestValue = None
self.type = 'holding register'
def __str__(self):
return ("[{0!s}, publishTopic: {1}, subscribeTopic: {2}, feedbackTopic: {3}, "
"writeRequestValue: {4!s}"
.format(super().__str__(), self.publishTopic, self.subscribeTopic, self.feedbackTopic,
self.writeRequestValue))
def process(self, client, pubQueue):
logger = logging.getLogger('HoldingRegisterDatapoint')
if self.writeRequestValue:
# perform write operation
logger.debug("Holding register, perform write operation")
self.processCount += 1
values = None
logger.debug("{0}: raw: {1!s}".format(self.label, self.writeRequestValue))
if self.converter and Converters.Converters[self.converter]['out']:
try:
values = Converters.Converters[self.converter]['out'](self.writeRequestValue)
logger.debug("{0}: converted: {1!s}".format(self.label, values))
except Exception as e:
raise DatapointException("Exception caught when trying to converter modbus data: {0!s}".format(e))
else:
values = [int(self.writeRequestValue)]
result = client.write_registers(address=self.address,
unit=self.unit,
values=values)
logger.debug("Write result: {0!s}".format(result))
self.writeRequestValue = None
else:
# perform read operation
logger.debug("Holding register, perform read operation")
self.processCount += 1
result = client.read_holding_registers(address=self.address,
count=self.count,
unit=self.unit)
if type(result) in [ExceptionResponse, ModbusIOException]:
self.errorCount += 1
raise DatapointException(result)
logger.debug("{0}: {1!s}".format(self.label, result.registers))
value = None
if self.converter and Converters.Converters[self.converter]['in']:
try:
value = Converters.Converters[self.converter]['in'](result.registers)
logger.debug("{0}: converted: {1!s}".format(self.label, value))
except Exception as e:
raise DatapointException("Exception caught when trying to converter modbus data: {0!s}".format(e))
else:
value = result.registers
if self.publishTopic:
pubQueue.put(MqttProcessor.PublishItem(self.publishTopic, str(value)))
self.lastContact = datetime.datetime.now()
def onMessage(self, value):
self.writeRequestValue = value
class CoilDatapoint(AbstractModbusDatapoint):
def __init__(self, label=None, unit=None, address=None, scanRate=None, publishTopic=None, subscribeTopic=None,
feedbackTopic=None):
super().__init__(label, unit, address, 1, scanRate, None)
self.argList = ['label', 'unit','address','scanRate','publishTopic', 'subscribeTopic', 'feedbackTopic']
self.publishTopic = publishTopic
self.subscribeTopic = subscribeTopic
self.feedbackTopic = feedbackTopic
self.writeRequestValue = None
self.type = 'coil'
def __str__(self):
return ("{0}, {1}: unit: {2}, address: {3}, scanRate: {4}, "
"enqueued: {5}, lastContact: {6}, errorCount: {7}, processCount: {8}, "
"publishTopic: {9}, subscribeTopic: {10}, feedbackTopic: {11}"
.format(self.type, self.label, self.unit, self.address,
self.scanRate, self.enqueued, self.lastContact,
self.errorCount, self.processCount,
self.publishTopic, self.subscribeTopic, self.feedbackTopic))
def onMessage(self, value):
self.writeRequestValue = value.decode()
def process(self, client, pubQueue):
logger = logging.getLogger('CoilDatapoint')
if self.writeRequestValue:
# perform write operation
logger.debug("Coil, perform write operation")
self.processCount += 1
logger.debug("{0}: raw: {1!s}".format(self.label, self.writeRequestValue))
value=None
if self.writeRequestValue in ['true', 'True', 'yes', 'Yes', 'On', 'on']:
value = True
elif self.writeRequestValue in ['false', 'False', 'no', 'No', 'Off', 'off']:
value = False
else:
self.writeRequestValue = None
raise DatapointException('illegal value {0!s} for coil write'.format(self.writeRequestValue))
result = client.write_coil(address=self.address,
unit=self.unit,
value=value)
logger.debug("Write result: {0!s}".format(result))
self.writeRequestValue = None
else:
# perform read operation
logger.debug("Coil, perform read operation")
self.processCount += 1
result = client.read_coils(address=self.address,
unit=self.unit)
if type(result) in [ExceptionResponse, ModbusIOException]:
self.errorCount += 1
raise DatapointException(result)
logger.debug("{0}: {1!s}".format(self.label, result.getBit(0)))
value = result.getBit(0)
if self.publishTopic:
pubQueue.put(MqttProcessor.PublishItem(self.publishTopic, str(value)))
self.lastContact = datetime.datetime.now()
class ReadOnlyDatapoint(AbstractModbusDatapoint):
def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None, publishTopic=None, converter=None):
super().__init__(label, unit, address, count, scanRate, converter)
self.argList = self.argList + ['updateOnly', 'publishTopic']
self.updateOnly = updateOnly
self.lastValue = None
self.publishTopic = publishTopic
def __str__(self):
return ("[{0!s}, updateOnly: {1}, publishTopic: {2}, lastValue: {3!s}"
.format(super().__str__(), self.updateOnly, self.publishTopic,
self.lastValue))
class InputRegisterDatapoint(ReadOnlyDatapoint):
def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None,
publishTopic=None, converter=None):
super().__init__(label, unit, address, count, scanRate, updateOnly, publishTopic, converter)
self.type = 'input register'
def process(self, client, pubQueue):
logger = logging.getLogger('InputRegisterDatapoint')
# perform read operation
logger.debug("Input register, perform read operation")
self.processCount += 1
result = client.read_input_registers(address=self.address,
count=self.count,
unit=self.unit)
if type(result) in [ExceptionResponse, ModbusIOException]:
self.errorCount += 1
raise DatapointException(result)
if not self.updateOnly or (result.registers != self.lastValue):
self.lastValue = result.registers
logger.debug("{0}: raw: {1!s}".format(self.label, result.registers))
value = None
if self.converter and Converters.Converters[self.converter]['in']:
try:
value = Converters.Converters[self.converter]['in'](result.registers)
logger.debug("{0}: converted: {1!s}".format(self.label, value))
except Exception as e:
raise DatapointException("Exception caught when trying to converter modbus data: {0!s}".format(e))
else:
value = result.registers
if self.publishTopic:
pubQueue.put(MqttProcessor.PublishItem(self.publishTopic, str(value)))
self.lastContact = datetime.datetime.now()
class DiscreteInputDatapoint(ReadOnlyDatapoint):
def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None,
publishTopic=None, converter=None, bitCount=8):
super().__init__(label, unit, address, count, scanRate, updateOnly, publishTopic, converter)
self.argList = self.argList + ['bitCount']
self.type = 'discrete input'
self.bitCount = bitCount
self.lastValues = [None] * self.bitCount
def __str__(self):
return ("[{0!s}, bitCount: {1}"
.format(super().__str__(), self.bitCount))
def process(self, client, pubQueue):
logger = logging.getLogger('DiscreteInputDatapoint')
# perform read operation
logger.debug("Discrete input, perform read operation")
self.processCount += 1
result = client.read_discrete_inputs(address=self.address,
count=self.count,
unit=self.unit)
if type(result) in [ExceptionResponse, ModbusIOException]:
self.errorCount += 1
raise DatapointException(result)
logger.debug("{0}: raw: {1!s}".format(self.label, result.bits))
for i in range(self.bitCount):
if not self.updateOnly or (result.getBit(i) != self.lastValues[i]):
self.lastValues[i] = result.getBit(i)
logger.debug("{0}, {1}: changed: {2!s}".format(self.label, i, result.getBit(i)))
if self.publishTopic:
pubQueue.put(MqttProcessor.PublishItem("{0}/{1}".format(self.publishTopic, i), str(result.getBit(i))))
self.lastContact = datetime.datetime.now()
class JsonifyEncoder(json.JSONEncoder):
def default(self, o):
res = None
try:
res = o.jsonify()
except (TypeError, AttributeError):
if type(o) == datetime.timedelta:
res = o.total_seconds()
else:
res = super().default(o)
return res
def datapointObjectHook(j):
if type(j) == dict and 'type' in j and 'args' in j:
klass = eval(j['type'])
o = klass(**j['args'])
return o
else:
return j
def saveRegisterList(registerList, registerListFile):
js = json.dumps(registerList, cls=JsonifyEncoder, sort_keys=True, indent=4)
with open(registerListFile, 'w') as f:
f.write(js)
def loadRegisterList(registerListFile):
with open(registerListFile, 'r') as f:
js = f.read()
registerList = json.loads(js, object_hook=datapointObjectHook)
return registerList

View File

@ -0,0 +1,36 @@
import threading
import datetime
from NotificationForwarder import AbstractNotificationReceiver
import logging
class ScanRateConsideringQueueFeeder(threading.Thread, AbstractNotificationReceiver):
def __init__(self, config, registers, queue):
super().__init__()
self.config = config
self.registers = registers
self.queue = queue
self.delayEvent = threading.Event()
self.daemon = True
self.logger = logging.getLogger('ScanRateConsideringQueueFeeder')
def getMinimalScanrate(self):
return min([r.scanRate.total_seconds() for r in self.registers if r.scanRate])
def receiveNotification(self, arg):
self.logger.info("ScanRateConsideringQueueFeeder:registersChanged")
self.delay = self.getMinimalScanrate()
def run(self):
self.delay = self.getMinimalScanrate()
while True:
registersToBeHandled = [
r for r in self.registers if ((not r.enqueued) and
(r.scanRate) and
((not r.lastContact) or
(r.lastContact + r.scanRate < datetime.datetime.now())))
]
registersToBeHandled.sort(key=lambda x : x.scanRate)
for r in registersToBeHandled:
self.queue.put(r)
r.enqueued = True
self.delayEvent.wait(self.delay)

View File

@ -0,0 +1,15 @@
import datetime
import RegisterDatapoint
import pickle
datapoints = [
RegisterDatapoint.InputRegisterDatapoint('Temperature', 5, 0x0001, 1, datetime.timedelta(seconds=1.0), False, 'Pub/Temperature'),
RegisterDatapoint.InputRegisterDatapoint('Humidity', 5, 0x0002, 1, datetime.timedelta(seconds=1.0), True, 'Pub/Humidity'),
RegisterDatapoint.DiscreteInputDatapoint('Switches', 4, 0x0000, 1, datetime.timedelta(seconds=1.0), True, 'Pub/Switches'),
]
with open('registers.pkl', 'wb') as f:
pickle.dump(datapoints, f)

6
src/loadRegisterFile.py Normal file
View File

@ -0,0 +1,6 @@
import RegisterDatapoint
registers = RegisterDatapoint.loadRegisterList('registers.json')
for r in registers:
print("{0!s}".format(r))

57
src/master.py Normal file
View File

@ -0,0 +1,57 @@
import CmdServer
import MqttProcessor
import CommunicationProcessor
import MyPriorityQueue
from queue import Queue
import NotificationForwarder
import Config
import ScanRateConsideringQueueFeeder
import datetime
import RegisterDatapoint
import pickle
import logging
if __name__ == "__main__":
config = Config.Config()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler(config.logFile)
fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
queue = MyPriorityQueue.MyPriorityQueue()
pubQueue = Queue()
nf = NotificationForwarder.NotificationForwarder()
logger.debug('infrastructure prepared')
datapoints = RegisterDatapoint.loadRegisterList(config.registerFile)
logger.debug('datapoints read')
cp = CommunicationProcessor.CommunicationProcessor(config, queue, pubQueue)
cp.start()
logger.debug('CommunicationProcessor started')
mp = MqttProcessor.MqttProcessor(config, datapoints, queue, pubQueue)
nf.register(mp)
mp.start()
logger.debug('MqttProcessor started')
qf = ScanRateConsideringQueueFeeder.ScanRateConsideringQueueFeeder(config, datapoints, queue)
nf.register(qf)
qf.start()
logger.debug('ScanRateConsideringQueueFeeder started')
cs = CmdServer.CmdServer(config, nf, datapoints)
cs.start()
logger.debug('CmdServer started')

122
src/registers.json Normal file
View File

@ -0,0 +1,122 @@
[
{
"args": {
"address": 1,
"converter": "dht20TOFloat",
"count": 1,
"label": "Temperature",
"publishTopic": "Pub/Temperature",
"scanRate": 10.0,
"unit": 5,
"updateOnly": false
},
"type": "InputRegisterDatapoint"
},
{
"args": {
"address": 2,
"converter": "dht20TOFloat",
"count": 1,
"label": "Humidity",
"publishTopic": "Pub/Humidity",
"scanRate": null,
"unit": 5,
"updateOnly": false
},
"type": "InputRegisterDatapoint"
},
{
"args": {
"address": 0,
"bitCount": 8,
"converter": null,
"count": 1,
"label": "Switches",
"publishTopic": "Pub/Switches",
"scanRate": null,
"unit": 4,
"updateOnly": false
},
"type": "DiscreteInputDatapoint"
},
{
"args": {
"address": 40010,
"converter": "uint32",
"count": 2,
"feedbackTopic": "FB/Counter1",
"label": "Counter1",
"publishTopic": "Pub/Counter1",
"scanRate": 1.0,
"subscribeTopic": "Sub/Counter1",
"unit": 4
},
"type": "HoldingRegisterDatapoint"
},
{
"args": {
"address": 40012,
"converter": "uint32",
"count": 2,
"feedbackTopic": "FB/Counter2",
"label": "Counter2",
"publishTopic": "Pub/Counter2",
"scanRate": null,
"subscribeTopic": "Pub/Counter2",
"unit": 4
},
"type": "HoldingRegisterDatapoint"
},
{
"args": {
"address": 40014,
"converter": "uint32",
"count": 2,
"feedbackTopic": "FB/Counter3",
"label": "Counter3",
"publishTopic": "Pub/Counter3",
"scanRate": null,
"subscribeTopic": "FB/Counter3",
"unit": 4
},
"type": "HoldingRegisterDatapoint"
},
{
"args": {
"address": 40016,
"converter": "uint32",
"count": 2,
"feedbackTopic": "FB/Counter4",
"label": "Counter4",
"publishTopic": "Pub/Counter4",
"scanRate": 1.0,
"subscribeTopic": "Sub/Counter4",
"unit": 4
},
"type": "HoldingRegisterDatapoint"
},
{
"args": {
"address": 0,
"feedbackTopic": "FB/Coil1",
"label": "Coil1",
"publishTopic": "Pub/Coil1",
"scanRate": 1.0,
"subscribeTopic": "Sub/Coil1",
"unit": 4
},
"type": "CoilDatapoint"
},
{
"args": {
"address": 1,
"feedbackTopic": "FB/Coil2",
"label": "Coil2",
"publishTopic": "Pub/Coil2",
"scanRate": 1.0,
"subscribeTopic": "Sub/Coil2",
"unit": 4
},
"type": "CoilDatapoint"
}
]

24
src/updateRegisterFile.py Normal file
View File

@ -0,0 +1,24 @@
import datetime
import RegisterDatapoint
import pickle
import json
with open('registers.pkl', 'rb') as f:
datapoints = pickle.load(f)
newDatapoints = []
for dp in datapoints:
ndp = type(dp)()
for k,v in dp.__dict__.items():
if k != 'logger':
ndp.__dict__[k] = v
newDatapoints.append(ndp)
js = json.dumps(newDatapoints, cls=RegisterDatapoint.JsonifyEncoder, sort_keys=True, indent=4)
print(js)
RegisterDatapoint.saveRegisterList(newDatapoints, 'registers.json')