Merge tag '2.9.0' into pkg/debian

Release version 2.9.0
This commit is contained in:
akira committed 2026-09-01 13:50:58 +02:00
commit fcecc9c770
17 files changed
+659 -12

No files matched your search

-3
View File
@@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Black"> <component name="Black">
<option name="enabledOnReformat" value="true" />
<option name="executionMode" value="BINARY" />
<option name="pathToExecutable" value="/opt/homebrew/bin/black" />
<option name="sdkName" value="Python 3.11 (revpimodio2)" /> <option name="sdkName" value="Python 3.11 (revpimodio2)" />
</component> </component>
<component name="JavaScriptSettings"> <component name="JavaScriptSettings">
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PyToolsState">
<option name="tools">
<map>
<entry key="black">
<value>
<ToolEntry>
<option name="enabled" value="true" />
<option name="discoveryMode" value="PATH" />
<option name="customPathToExecutable" value="/opt/homebrew/bin/black" />
</ToolEntry>
</value>
</entry>
</map>
</option>
</component>
</project>
+2 -1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Metadata of package.""" """Metadata of package."""
__author__ = "Sven Sager <akira@revpimodio.org>" __author__ = "Sven Sager <akira@revpimodio.org>"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
__version__ = "2.8.1" __version__ = "2.9.0"
+1
View File
@@ -12,6 +12,7 @@ With the defined names, the desired data is accessed directly.
The user can register functions as events for all IOs. The module The user can register functions as events for all IOs. The module
executes these when data changes. executes these when data changes.
""" """
__all__ = [ __all__ = [
"IOEvent", "IOEvent",
"RevPiModIO", "RevPiModIO",
+1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Internal functions and values for this package.""" """Internal functions and values for this package."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
+1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Maps the App section from piCtory.""" """Maps the App section from piCtory."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
+22 -2
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Module for managing devices.""" """Module for managing devices."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
@@ -354,8 +355,10 @@ class Device(object):
elif bool(dict_io[key][7]): elif bool(dict_io[key][7]):
# Use IOBase for bit values # Use IOBase for bit values
io_new = IOBase(self, dict_io[key], iotype, "little", False) io_new = IOBase(self, dict_io[key], iotype, "little", False)
elif isinstance(self, DioModule) and dict_io[key][3] in self._lst_counter: elif (isinstance(self, DioModule) or isinstance(self, MioModule)) and dict_io[key][
# Counter IO on a DI or DIO 3
] in self._lst_counter:
# Counter IO on a DI, DIO or MIO module
io_new = IntIOCounter( io_new = IntIOCounter(
self._lst_counter.index(dict_io[key][3]), self._lst_counter.index(dict_io[key][3]),
self, self,
@@ -2030,6 +2033,23 @@ class DioModule(Device):
super().__init__(parentmodio, dict_device, simulator=simulator) super().__init__(parentmodio, dict_device, simulator=simulator)
class MioModule(Device):
"""Represents a MIO module."""
__slots__ = "_lst_counter"
def __init__(self, parentmodio, dict_device, simulator=False):
"""
Extended device class to recognize IntIOCounter.
:rev: :func:`Device.__init__()`
"""
# String list of byte addresses that are a counter.
self._lst_counter = list(map(str, range(9, 17, 2)))
super().__init__(parentmodio, dict_device, simulator=simulator)
class RoModule(Device): class RoModule(Device):
"""Relais output (RO) module with""" """Relais output (RO) module with"""
+1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Error classes of RevPiModIO.""" """Error classes of RevPiModIO."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
+1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""RevPiModIO helper classes and tools.""" """RevPiModIO helper classes and tools."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
+20 -5
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""RevPiModIO module for managing IOs.""" """RevPiModIO module for managing IOs."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
@@ -1130,8 +1131,12 @@ class IntIOCounter(IntIO):
# Load base class # Load base class
super().__init__(parentdevice, valuelist, iotype, byteorder, signed) super().__init__(parentdevice, valuelist, iotype, byteorder, signed)
def reset(self) -> None: def reset(self) -> bool:
"""Resets the counter of the input.""" """
Resets the counter of the input.
:return: True if reset was successful, False otherwise
"""
if self._parentdevice._modio._monitoring: if self._parentdevice._modio._monitoring:
raise RuntimeError("can not reset counter, while system is in monitoring mode") raise RuntimeError("can not reset counter, while system is in monitoring mode")
if self._parentdevice._modio._simulator: if self._parentdevice._modio._simulator:
@@ -1145,6 +1150,7 @@ class IntIOCounter(IntIO):
ioctl(self._parentdevice._modio._myfh, 19220, self.__ioctl_arg) ioctl(self._parentdevice._modio._myfh, 19220, self.__ioctl_arg)
except Exception as e: except Exception as e:
self._parentdevice._modio._gotioerror("iorst", e) self._parentdevice._modio._gotioerror("iorst", e)
return False
elif hasattr(self._parentdevice._modio._myfh, "ioctl"): elif hasattr(self._parentdevice._modio._myfh, "ioctl"):
# IOCTL over network # IOCTL over network
@@ -1153,6 +1159,7 @@ class IntIOCounter(IntIO):
self._parentdevice._modio._myfh.ioctl(19220, self.__ioctl_arg) self._parentdevice._modio._myfh.ioctl(19220, self.__ioctl_arg)
except Exception as e: except Exception as e:
self._parentdevice._modio._gotioerror("net_iorst", e) self._parentdevice._modio._gotioerror("net_iorst", e)
return False
else: else:
# Simulate IOCTL in file # Simulate IOCTL in file
@@ -1161,6 +1168,9 @@ class IntIOCounter(IntIO):
self._parentdevice._modio._simulate_ioctl(19220, self.__ioctl_arg) self._parentdevice._modio._simulate_ioctl(19220, self.__ioctl_arg)
except Exception as e: except Exception as e:
self._parentdevice._modio._gotioerror("file_iorst", e) self._parentdevice._modio._gotioerror("file_iorst", e)
return False
return True
class IntIOReplaceable(IntIO): class IntIOReplaceable(IntIO):
@@ -1275,7 +1285,8 @@ class RelaisOutput(IOBase):
This function is only available locally on a Revolution Pi. This This function is only available locally on a Revolution Pi. This
function cannot be used via RevPiNetIO. function cannot be used via RevPiNetIO.
:return: Integer of switching cycles as single value or tuple of all :return: Integer of switching cycles as single value or tuple of all. The
value -1 indicates an error.
""" """
# Using ioctl request K+29 = 19229 # Using ioctl request K+29 = 19229
if self._parentdevice._modio._run_on_pi: if self._parentdevice._modio._run_on_pi:
@@ -1288,10 +1299,14 @@ class RelaisOutput(IOBase):
self.__ioctl_arg, self.__ioctl_arg,
) )
except Exception as e: except Exception as e:
# If not implemented, we return the max value and set an error
ioctl_return_value = b"\xff" * struct.calcsize(self.__ioctl_arg_format)
self._parentdevice._modio._gotioerror("rocounter", e) self._parentdevice._modio._gotioerror("rocounter", e)
# To report an error, return -1 values
if self._bitaddress == -1:
return -1, -1, -1, -1
else:
return -1
elif hasattr(self._parentdevice._modio._myfh, "ioctl"): elif hasattr(self._parentdevice._modio._myfh, "ioctl"):
# IOCTL over network # IOCTL over network
""" """
+14 -1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""RevPiModIO main class for piControl0 access.""" """RevPiModIO main class for piControl0 access."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
@@ -403,6 +404,9 @@ class RevPiModIO(object):
elif pt == ProductType.RO: elif pt == ProductType.RO:
# RO # RO
dev_new = devicemodule.RoModule(self, device, simulator=self._simulator) dev_new = devicemodule.RoModule(self, device, simulator=self._simulator)
elif pt == ProductType.MIO:
# MIO
dev_new = devicemodule.MioModule(self, device, simulator=self._simulator)
else: else:
# All other IO devices # All other IO devices
dev_new = devicemodule.Device(self, device, simulator=self._simulator) dev_new = devicemodule.Device(self, device, simulator=self._simulator)
@@ -1530,7 +1534,15 @@ class RevPiModIODriver(RevPiModIOSelected):
) )
def run_plc(func, cycletime=50, replace_io_file=None, debug=True, procimg=None, configrsc=None): def run_plc(
func,
cycletime=50,
replace_io_file=None,
debug=True,
procimg=None,
configrsc=None,
shared_procimg=False,
):
""" """
Run Revoluton Pi as real plc with cycle loop and exclusive IO access. Run Revoluton Pi as real plc with cycle loop and exclusive IO access.
@@ -1557,6 +1569,7 @@ def run_plc(func, cycletime=50, replace_io_file=None, debug=True, procimg=None,
debug=debug, debug=debug,
procimg=procimg, procimg=procimg,
configrsc=configrsc, configrsc=configrsc,
shared_procimg=shared_procimg,
) )
rpi.handlesignalend() rpi.handlesignalend()
return rpi.cycleloop(func, cycletime) return rpi.cycleloop(func, cycletime)
+1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""RevPiModIO main class for network access.""" """RevPiModIO main class for network access."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
+1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Pictory aliases for IO values.""" """Pictory aliases for IO values."""
__author__ = "Théo Rozier" __author__ = "Théo Rozier"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
+1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Maps the Summary section from piCtory.""" """Maps the Summary section from piCtory."""
__author__ = "Sven Sager" __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv2" __license__ = "LGPLv2"
+309
View File
@@ -0,0 +1,309 @@
{
"App": {
"name": "PiCtory",
"version": "2.17.3",
"saveTS": "20260901080641",
"language": "en",
"layout": {
"north": {
"size": 70,
"initClosed": false,
"initHidden": false
},
"south": {
"size": 353,
"initClosed": false,
"initHidden": false,
"children": {
"layout1": {
"east": {
"size": 500,
"initClosed": false,
"initHidden": false
}
}
}
},
"east": {
"size": 70,
"initClosed": true,
"initHidden": false,
"children": {}
},
"west": {
"size": 340,
"initClosed": false,
"initHidden": false,
"children": {
"layout1": {}
}
}
}
},
"Summary": {
"inpTotal": 7,
"outTotal": 8
},
"Devices": [
{
"GUID": "fa3b69a8-77f1-7f15-d9f0-339646253ace",
"id": "device_RevPiConnect5_20240315_1_0_001",
"type": "BASE",
"productType": "138",
"position": "0",
"name": "RevPi Connect 5",
"bmk": "RevPi Connect 5",
"inpVariant": 0,
"outVariant": 0,
"comment": "This is a RevPi Connect 5 Device",
"offset": 0,
"inp": {
"0": [
"RevPiStatus",
"0",
"8",
"0",
true,
"0000",
"",
""
],
"1": [
"RevPiIOCycle",
"0",
"8",
"1",
true,
"0001",
"",
""
],
"2": [
"RS485ErrorCnt",
"0",
"16",
"2",
false,
"0002",
"",
""
],
"3": [
"Core_Temperature",
"0",
"8",
"4",
false,
"0003",
"",
""
],
"4": [
"Core_Frequency",
"0",
"8",
"5",
false,
"0004",
"",
""
]
},
"out": {
"0": [
"RevPiReservedByte",
"0",
"8",
"6",
false,
"0005",
"",
""
],
"1": [
"RS485ErrorLimit1",
"10",
"16",
"7",
false,
"0006",
"",
""
],
"2": [
"RS485ErrorLimit2",
"1000",
"16",
"9",
false,
"0007",
"",
""
],
"3": [
"RevPiLED",
"0",
"16",
"11",
true,
"0008",
"",
""
]
},
"mem": {},
"extend": {}
},
{
"GUID": "53c792e0-ec3d-8047-bb0c-1afa6772873f",
"id": "device_RevPiRO_20231018_1_0_001",
"type": "LEFT_RIGHT",
"productType": "137",
"position": "32",
"name": "RevPi RO",
"bmk": "RevPi RO",
"inpVariant": 0,
"outVariant": 0,
"comment": "",
"offset": 13,
"inp": {
"0": [
"Status",
"0",
"8",
"0",
false,
"0000",
"",
""
]
},
"out": {
"0": [
"RelayOutput_1",
"0",
"1",
"1",
true,
"0001",
"",
"0"
],
"1": [
"RelayOutput_2",
"0",
"1",
"1",
true,
"0002",
"",
"1"
],
"2": [
"RelayOutput_3",
"0",
"1",
"1",
true,
"0003",
"",
"2"
],
"3": [
"RelayOutput_4",
"0",
"1",
"1",
true,
"0004",
"",
"3"
],
"4": [
"RelayOutputPadding_5",
"0",
"1",
"1",
true,
"0005",
"",
"4"
],
"5": [
"RelayOutputPadding_6",
"0",
"1",
"1",
true,
"0006",
"",
"5"
],
"6": [
"RelayOutputPadding_7",
"0",
"1",
"1",
true,
"0007",
"",
"6"
],
"7": [
"RelayOutputPadding_8",
"0",
"1",
"1",
true,
"0008",
"",
"7"
]
},
"mem": {
"0": [
"RelayCycleWarningThreshold_1",
"0",
"32",
"2",
false,
"0009",
"",
""
],
"1": [
"RelayCycleWarningThreshold_2",
"0",
"32",
"6",
false,
"0010",
"",
""
],
"2": [
"RelayCycleWarningThreshold_3",
"0",
"32",
"10",
false,
"0011",
"",
""
],
"3": [
"RelayCycleWarningThreshold_4",
"0",
"32",
"14",
false,
"0012",
"",
""
]
},
"extend": {}
}
],
"Connections": []
}
+239
View File
@@ -0,0 +1,239 @@
{
"App": {
"name": "PiCtory",
"version": "2.17.3",
"saveTS": "20260901080758",
"language": "en",
"layout": {
"north": {
"size": 70,
"initClosed": false,
"initHidden": false
},
"south": {
"size": 353,
"initClosed": false,
"initHidden": false,
"children": {
"layout1": {
"east": {
"size": 500,
"initClosed": false,
"initHidden": false
}
}
}
},
"east": {
"size": 70,
"initClosed": true,
"initHidden": false,
"children": {}
},
"west": {
"size": 340,
"initClosed": false,
"initHidden": false,
"children": {
"layout1": {}
}
}
}
},
"Summary": {
"inpTotal": 7,
"outTotal": 8
},
"Devices": [
{
"GUID": "fa3b69a8-77f1-7f15-d9f0-339646253ace",
"id": "device_RevPiConnect5_20240315_1_0_001",
"type": "BASE",
"productType": "138",
"position": "0",
"name": "RevPi Connect 5",
"bmk": "RevPi Connect 5",
"inpVariant": 0,
"outVariant": 0,
"comment": "This is a RevPi Connect 5 Device",
"offset": 0,
"inp": {
"0": [
"RevPiStatus",
"0",
"8",
"0",
true,
"0000",
"",
""
],
"1": [
"RevPiIOCycle",
"0",
"8",
"1",
true,
"0001",
"",
""
],
"2": [
"RS485ErrorCnt",
"0",
"16",
"2",
false,
"0002",
"",
""
],
"3": [
"Core_Temperature",
"0",
"8",
"4",
false,
"0003",
"",
""
],
"4": [
"Core_Frequency",
"0",
"8",
"5",
false,
"0004",
"",
""
]
},
"out": {
"0": [
"RevPiReservedByte",
"0",
"8",
"6",
false,
"0005",
"",
""
],
"1": [
"RS485ErrorLimit1",
"10",
"16",
"7",
false,
"0006",
"",
""
],
"2": [
"RS485ErrorLimit2",
"1000",
"16",
"9",
false,
"0007",
"",
""
],
"3": [
"RevPiLED",
"0",
"16",
"11",
true,
"0008",
"",
""
]
},
"mem": {},
"extend": {}
},
{
"GUID": "53c792e0-ec3d-8047-bb0c-1afa6772873f",
"id": "device_RevPiRO_20231018_1_0_001",
"type": "LEFT_RIGHT",
"productType": "137",
"position": "32",
"name": "RevPi RO",
"bmk": "RevPi RO",
"inpVariant": 0,
"outVariant": 1,
"comment": "",
"offset": 13,
"inp": {
"0": [
"Status_i02",
"0",
"8",
"0",
false,
"0000",
"",
""
]
},
"out": {
"0": [
"RelayOutputs",
"0",
"8",
"1",
true,
"0001",
"",
""
]
},
"mem": {
"0": [
"RelayCycleWarningThreshold_1_i02",
"0",
"32",
"2",
false,
"0002",
"",
""
],
"1": [
"RelayCycleWarningThreshold_2_i02",
"0",
"32",
"6",
false,
"0003",
"",
""
],
"2": [
"RelayCycleWarningThreshold_3_i02",
"0",
"32",
"10",
false,
"0004",
"",
""
],
"3": [
"RelayCycleWarningThreshold_4_i02",
"0",
"32",
"14",
false,
"0005",
"",
""
]
},
"extend": {}
}
],
"Connections": []
}
+27
View File
@@ -105,3 +105,30 @@ class TestIos(TestRevPiModIO):
self.assertEqual(rpi.device[65]._ba_devdata[32:38], b"\x20\x00\x00\x00\x00\x00") self.assertEqual(rpi.device[65]._ba_devdata[32:38], b"\x20\x00\x00\x00\x00\x00")
rpi.io.OutBit_48.value = True rpi.io.OutBit_48.value = True
self.assertEqual(rpi.device[65]._ba_devdata[32:38], b"\x20\x00\x00\x00\x00\x80") self.assertEqual(rpi.device[65]._ba_devdata[32:38], b"\x20\x00\x00\x00\x00\x80")
def test_get_switching_cycles(self):
rpi = self.modio(configrsc="config_ro_multi.rsc")
self.assertEqual(rpi.io.RelayOutput_1.get_switching_cycles(), 0)
self.assertEqual(rpi.io.RelayOutput_2.get_switching_cycles(), 0)
self.assertEqual(rpi.io.RelayOutput_3.get_switching_cycles(), 0)
self.assertEqual(rpi.io.RelayOutput_4.get_switching_cycles(), 0)
# This will use ioctl calls
rpi._run_on_pi = True
with self.assertWarnsRegex(RuntimeWarning, r"'rocounter' and count \d"):
self.assertEqual(rpi.io.RelayOutput_1.get_switching_cycles(), -1)
self.assertEqual(rpi.io.RelayOutput_2.get_switching_cycles(), -1)
self.assertEqual(rpi.io.RelayOutput_3.get_switching_cycles(), -1)
self.assertEqual(rpi.io.RelayOutput_4.get_switching_cycles(), -1)
self.assertEqual(rpi.ioerrors, 4)
rpi = self.modio(configrsc="config_ro_single.rsc")
self.assertEqual(rpi.io.RelayOutputs.get_switching_cycles(), (0, 0, 0, 0))
# This will use ioctl calls
rpi._run_on_pi = True
with self.assertWarnsRegex(RuntimeWarning, r"'rocounter' and count \d"):
self.assertEqual(rpi.io.RelayOutputs.get_switching_cycles(), (-1, -1, -1, -1))
self.assertEqual(rpi.ioerrors, 1)