diff --git a/awlsim/core/systemblocks/system_sfc.pxd.in b/awlsim/core/systemblocks/system_sfc.pxd.in index 1ec1fe587..906cb9956 100644 --- a/awlsim/core/systemblocks/system_sfc.pxd.in +++ b/awlsim/core/systemblocks/system_sfc.pxd.in @@ -3,6 +3,7 @@ from awlsim.core.systemblocks.system_sfc_m4 cimport * from awlsim.core.systemblocks.system_sfc_m3 cimport * from awlsim.core.systemblocks.system_sfc_m2 cimport * from awlsim.core.systemblocks.system_sfc_m1 cimport * +from awlsim.core.systemblocks.system_sfc_4 cimport * from awlsim.core.systemblocks.system_sfc_21 cimport * from awlsim.core.systemblocks.system_sfc_46 cimport * from awlsim.core.systemblocks.system_sfc_47 cimport * diff --git a/awlsim/core/systemblocks/system_sfc.py b/awlsim/core/systemblocks/system_sfc.py index e28fb709e..76d06e5f1 100644 --- a/awlsim/core/systemblocks/system_sfc.py +++ b/awlsim/core/systemblocks/system_sfc.py @@ -27,6 +27,7 @@ from awlsim.core.systemblocks.system_sfc_m3 import * #+cimport from awlsim.core.systemblocks.system_sfc_m2 import * #+cimport from awlsim.core.systemblocks.system_sfc_m1 import * #+cimport +from awlsim.core.systemblocks.system_sfc_4 import * #+cimport from awlsim.core.systemblocks.system_sfc_21 import * #+cimport from awlsim.core.systemblocks.system_sfc_46 import * #+cimport from awlsim.core.systemblocks.system_sfc_47 import * #+cimport @@ -42,6 +43,7 @@ -2 : SFCm2, # __REBOOT -1 : SFCm1, # __SFC_NOP + 4 : SFC4, # READ_RTM 21 : SFC21, # FILL 46 : SFC46, # STP 47 : SFC47, # WAIT diff --git a/awlsim/core/systemblocks/system_sfc_4.pxd.in b/awlsim/core/systemblocks/system_sfc_4.pxd.in new file mode 100644 index 000000000..a3a9f92a8 --- /dev/null +++ b/awlsim/core/systemblocks/system_sfc_4.pxd.in @@ -0,0 +1,7 @@ +from awlsim.common.cython_support cimport * +from awlsim.core.systemblocks.systemblocks cimport * + +cdef class SFC4(SFC): + cdef public list __meters + + cpdef run(self) diff --git a/awlsim/core/systemblocks/system_sfc_4.py b/awlsim/core/systemblocks/system_sfc_4.py new file mode 100644 index 000000000..076e9fbe8 --- /dev/null +++ b/awlsim/core/systemblocks/system_sfc_4.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# +# AWL simulator - SFCs +# +# Copyright 2026 QuackS7 contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +from __future__ import division, absolute_import, print_function, unicode_literals +#from awlsim.common.cython_support cimport * #@cy +from awlsim.common.compat import * + +from awlsim.common.exceptions import * +from awlsim.common.util import * + +from awlsim.core.systemblocks.systemblocks import * #+cimport +from awlsim.core.blockinterface import * +from awlsim.core.memory import * #+cimport + + +class SFC4(SFC): #+cdef + name = (4, "READ_RTM", "read runtime meter") + + interfaceFields = { + BlockInterfaceField.FTYPE_IN : ( + BlockInterfaceField(name="NR", dataType="BYTE"), + ), + BlockInterfaceField.FTYPE_OUT : ( + BlockInterfaceField(name="RET_VAL", dataType="INT"), + BlockInterfaceField(name="CQ", dataType="BOOL"), + BlockInterfaceField(name="CV", dataType="INT"), + ), + } + + def __init__(self, cpu): + SFC.__init__(self, cpu) + # 8 runtime meters, each (running: bool, hours: int). + # Until SFC 2 SET_RTM / SFC 3 CTRL_RTM are added, every meter + # reads back (False, 0) — the correct state for a CPU that has + # never started any runtime meter. + self.__meters = [[False, 0] for _ in range(8)] + + def run(self): #+cpdef +#@cy cdef S7StatusWord s +#@cy cdef uint32_t nr + + s = self.cpu.statusWord + + nr = AwlMemoryObject_asScalar( + self.fetchInterfaceFieldByName("NR")) + + if nr > 7: + # Siemens SFC_e §6.5 mandates flat specific code 0x8080 for + # NR out of range, not the general-code E_RPARM encoding. + # See docs/siemens/SFC_e (1).pdf §6.5 and rowlf's FLAG-B + # ruling (2026-04-17, user-ratified). + self.storeInterfaceFieldByName("RET_VAL", + make_AwlMemoryObject_fromScalar(0x8080, 16)) + self.storeInterfaceFieldByName("CQ", + make_AwlMemoryObject_fromScalar(0, 1)) + self.storeInterfaceFieldByName("CV", + make_AwlMemoryObject_fromScalar(0, 16)) + s.BIE = 0 + return + + running, hours = self.__meters[nr] + + self.storeInterfaceFieldByName("RET_VAL", + make_AwlMemoryObject_fromScalar(0, 16)) + self.storeInterfaceFieldByName("CQ", + make_AwlMemoryObject_fromScalar(1 if running else 0, 1)) + self.storeInterfaceFieldByName("CV", + make_AwlMemoryObject_fromScalar(hours & 0xFFFF, 16)) + s.BIE = 1 diff --git a/tests/tc500_systemblocks/sfc/sfc4.awl b/tests/tc500_systemblocks/sfc/sfc4.awl new file mode 100644 index 000000000..b9e201455 --- /dev/null +++ b/tests/tc500_systemblocks/sfc/sfc4.awl @@ -0,0 +1,85 @@ +ORGANIZATION_BLOCK OB 1 + VAR_TEMP + NR_TMP : BYTE; + RET_TMP : INT; + CQ_TMP : BOOL; + CV_TMP : INT; + END_VAR +BEGIN + // Test SFC 4: READ_RTM + + + // Valid meter number (0) on a fresh CPU: meter stopped, value 0. + L B#16#00 + T #NR_TMP + CALL SFC 4 ( + NR := #NR_TMP, + RET_VAL := #RET_TMP, + CQ := #CQ_TMP, + CV := #CV_TMP, + ) + __ASSERT== __STW BIE, 1 + L #RET_TMP + __ASSERT== __ACCU 1, W#16#0000 + L #CV_TMP + __ASSERT== __ACCU 1, W#16#0000 + U #CQ_TMP + __ASSERT== __STW VKE, 0 + + + // Valid meter number (7, the maximum) - same expected state. + L B#16#07 + T #NR_TMP + CALL SFC 4 ( + NR := #NR_TMP, + RET_VAL := #RET_TMP, + CQ := #CQ_TMP, + CV := #CV_TMP, + ) + __ASSERT== __STW BIE, 1 + L #RET_TMP + __ASSERT== __ACCU 1, W#16#0000 + L #CV_TMP + __ASSERT== __ACCU 1, W#16#0000 + U #CQ_TMP + __ASSERT== __STW VKE, 0 + + + // Invalid meter number (8): E_RPARM on parameter 1, BIE cleared. + L B#16#08 + T #NR_TMP + CALL SFC 4 ( + NR := #NR_TMP, + RET_VAL := #RET_TMP, + CQ := #CQ_TMP, + CV := #CV_TMP, + ) + __ASSERT== __STW BIE, 0 + L #RET_TMP + __ASSERT== __ACCU 1, W#16#8080 + L #CV_TMP + __ASSERT== __ACCU 1, W#16#0000 + U #CQ_TMP + __ASSERT== __STW VKE, 0 + + + // Invalid meter number (255, max BYTE): same error path. + L B#16#FF + T #NR_TMP + CALL SFC 4 ( + NR := #NR_TMP, + RET_VAL := #RET_TMP, + CQ := #CQ_TMP, + CV := #CV_TMP, + ) + __ASSERT== __STW BIE, 0 + L #RET_TMP + __ASSERT== __ACCU 1, W#16#8080 + L #CV_TMP + __ASSERT== __ACCU 1, W#16#0000 + U #CQ_TMP + __ASSERT== __STW VKE, 0 + + + CALL SFC 46 // STOP CPU +END_ORGANIZATION_BLOCK