4040 DatagramService ,
4141)
4242from openlcb .convert import Convert
43+ from openlcb .memoryconfigurationheader import MemoryConfigurationHeader , MemorySpaceIndex
44+ from openlcb .memorymanager import MemoryManager
4345from openlcb .nodeid import NodeID
4446
4547logger = getLogger (__name__ )
4648
47-
49+ MODE_BYTES [ '' ]
4850MODE_BYTES = {
49- 'Read_Command' : {0x40 , 0x41 , 0x42 , 0x43 },
51+ # order determines meaning for lists (See )
52+ 'Read_Command' : {0x40 , 0x41 , 0x42 , 0x43 }, # TODO: Use memoryManagers
5053 'Read_Reply' : {0x50 , 0x51 , 0x52 , 0x53 },
51- 'Read_Stream_Command' : {0x60 , 0x61 , 0x62 , 0x63 },
52- 'Read_Stream_Reply' : {0x70 , 0x71 , 0x72 , 0x73 },
53- 'Write_Command' : { 0x00 , 0x01 , 0x02 , 0x03 },
54+ 'Read_Stream_Command' : {0x60 , 0x61 , 0x62 , 0x63 }, # TODO: Use memoryManagers
55+ 'Read_Stream_Reply' : {0x70 , 0x71 , 0x72 , 0x73 }, # TODO
56+ 'Write_Command' : [ 0x00 , 0x01 , 0x02 , 0x03 ], # TODO: Use memoryManagers
5457 'Write_Reply' : {0x10 , 0x11 , 0x12 , 0x13 },
55- 'Write_Under_Mask_Command' : {0x08 , 0x09 , 0x0A , 0x0B },
58+ 'Write_Under_Mask_Command' : {0x08 , 0x09 , 0x0A , 0x0B }, # TODO: Use memoryManagers
5659 'Write_Stream_Command' : {0x20 , 0x21 , 0x22 , 0x23 },
57- 'Write_Stream_Reply' : {0x30 , 0x31 , 0x32 , 0x33 },
60+ 'Write_Stream_Reply' : {0x30 , 0x31 , 0x32 , 0x33 }, # TODO
61+ 'Get_Address_Space_Info_Command' : {0x84 , },
62+ 'Get_Address_Space_Info_Reply' : {0x86 , 0x87 , },
63+ 'Lock_Reserve_Command' : {0x88 , },
5864}
5965
66+
6067MODE_ERROR_BYTES = {
6168 'Read_Reply' : {0x58 , 0x59 , 0x5A , 0x5B },
6269 'Read_Stream_Reply' : {0x78 , 0x79 , 0x7A , 0x7B },
@@ -85,10 +92,10 @@ class MemorySpace(Enum):
8592 (See OpenLCB Memory Configuration Standard 4.2).
8693 """
8794 Uninitialized = - 1
88- CDI = 0xFF # decodes to 0x03
8995 FDI = 0xFA
90- All = 0xFE
9196 Configuration = 0xFD
97+ All = 0xFE
98+ CDI = 0xFF # decodes to 0x03
9299
93100 @classmethod
94101 def fromNumber (cls , num : int ):
@@ -101,6 +108,22 @@ def fromNumber(cls, num: int):
101108 return member
102109 return None
103110
111+ @classmethod
112+ def fromIndex (cls , msi : MemorySpaceIndex ):
113+ """Return the MemorySpace member with the given numeric value,
114+ or None if no match is found.
115+ """
116+ assert isinstance (msi , MemorySpaceIndex )
117+ if msi is MemorySpaceIndex .Custom :
118+ return None
119+ elif msi is MemorySpaceIndex .Configuration :
120+ return cls .Configuration
121+ elif msi is MemorySpaceIndex .All :
122+ return cls .All
123+ elif msi is MemorySpaceIndex .CDI :
124+ return cls .CDI
125+ return None
126+
104127
105128class MemoryReadMemo :
106129 """Memo carries request and reply.
@@ -211,10 +234,16 @@ def parseReplyDatagram(memo: Union[MemoryReadMemo, MemoryWriteMemo],
211234 "Datagram is truncated to 1 byte:"
212235 f" it is { hex (dmemo .data [0 ])} " )
213236 return
214- (hasByte6 , _ ) = Convert .deserializeMC2ndByte (dmemo .data [1 ])
237+ mcHeader = MemoryConfigurationHeader .fromMC2ndByte (
238+ dmemo .data [1 ],
239+ # space=memo.space,
240+ )
215241 offset = 6
216242 error = None
217- if hasByte6 :
243+ assert mcHeader .spaceIndex is not MemorySpaceIndex .Uninitialized
244+ if mcHeader .spaceIndex is MemorySpaceIndex .Custom :
245+ # mcHeader.customSpace = memo.space
246+ mcHeader .customSpace = dmemo .data [6 ]
218247 offset = 7
219248 memo .error = None
220249 memo .errorCode = None
@@ -256,9 +285,15 @@ def parseReplyDatagram(memo: Union[MemoryReadMemo, MemoryWriteMemo],
256285 error += f" ({ list (dmemo .data [message_idx :])} )"
257286 else :
258287 error = f"(2nd byte = { hex (dmemo .data [1 ])} )"
259- error += f" (hasByte6={ hasByte6 } )"
260- if hasByte6 :
261- error += f" (space={ hex (dmemo .data [6 ])} )"
288+ error += f" (spaceIndex={ mcHeader .spaceIndex } )"
289+ if mcHeader .spaceIndex is mcHeader .customSpace :
290+ if mcHeader .customSpace is not None :
291+ if mcHeader .customSpace != dmemo .data [6 ]:
292+ error += f" (mcHeader.customSpace={ hex (mcHeader .customSpace )} != space={ hex (dmemo .data [6 ])} !)" # noqa: E501
293+ else :
294+ error += f" (mcHeader.customSpace={ hex (mcHeader .customSpace )} )"
295+ else :
296+ error += f" (space={ hex (dmemo .data [6 ])} mcHeader.customSpace=None!)" # noqa: E501
262297 memo .error = error
263298
264299
@@ -268,6 +303,12 @@ class MemoryService:
268303
269304 Args:
270305 service (DatagramService): See DatagramService.
306+
307+ Attributes:
308+ memoryManagers (dict[str, MemoryManager]): The storage where
309+ other nodes can read and write memory. Each element can be
310+ changed to a specific nodeid's memory manager. They key is
311+ the NodeID in string form (dotted notation).
271312 """
272313
273314 def __init__ (self , service : DatagramService ):
@@ -280,6 +321,7 @@ def __init__(self, service: DatagramService):
280321 self .service .registerDatagramReceivedListener (
281322 self .datagramReceivedListener
282323 )
324+ self .memoryManagers = {} # type: dict[str, MemoryManager]
283325
284326 def requestMemoryRead (self , memo , stream : bool = False ):
285327 # type: (MemoryReadMemo, Optional[bool]) -> None
@@ -308,16 +350,10 @@ def requestMemoryReadNext(self, memo, stream: bool = False):
308350 memo (MemoryReadMemo): Request to send.
309351 """
310352 assert isinstance (stream , bool )
311- hasByte6 = False # if custom space is defined in byte 6
312- flag = 0
313- (hasByte6 , flag ) = Convert .serializeSpace (memo .space )
314- if stream :
315- # Encoding: 0x60=custom, 0x61=0xFD, 0x62=0xFE, 0x63=0xFF
316- spaceFlag = 0x60 if hasByte6 else (flag | 0x60 )
317- else :
318- # Encoding: 0x40=custom, 0x41=0xFD, 0x42=0xFE, 0x43=0xFF
319- spaceFlag = 0x40 if hasByte6 else (flag | 0x40 ) # | 0b11111100
320- # ^ In else case, flag is 1-3, so re-add 0xFC (0b11111100)
353+ mcHeader = MemoryConfigurationHeader (memo .space )
354+ assert mcHeader .spaceIndex is not None
355+ spaceFlag = (0x60 if stream else 0x40 ) | mcHeader .spaceIndex .value
356+ # NOTE: Why was there commented: | 0xFC (0b11111100) if not stream?
321357 addr2 = ((memo .address >> 24 ) & 0xFF )
322358 addr3 = ((memo .address >> 16 ) & 0xFF )
323359 addr4 = ((memo .address >> 8 ) & 0xFF )
@@ -326,7 +362,7 @@ def requestMemoryReadNext(self, memo, stream: bool = False):
326362 DatagramService .ProtocolID .MemoryOperation .value , spaceFlag ,
327363 addr2 , addr3 , addr4 , addr5 ])
328364 # NOTE: list[int] is ok for bytearray extend (`+` requires cast)
329- if hasByte6 :
365+ if mcHeader . customSpace is not None :
330366 assert memo .space <= 0xFF , f"Space { memo .space } out of byte range"
331367 data .extend ([(memo .space & 0xFF )])
332368 data .extend ([memo .size ])
@@ -352,7 +388,6 @@ def datagramReceivedListener(self, dmemo: DatagramReadMemo) -> bool:
352388 if self .service .datagramType (dmemo .data ) \
353389 != DatagramService .ProtocolID .MemoryOperation :
354390 return False
355-
356391 # datagram must has a command value
357392 if len (dmemo .data ) < 2 :
358393 logger .error ("Memory service datagram too short: {}"
@@ -449,14 +484,8 @@ def requestMemoryWrite(self, memo: MemoryWriteMemo, stream: bool = False):
449484 self .writeMemos .append (memo )
450485 # create & send a write datagram
451486 hasByte6 = False # if custom space is defined in byte 6
452- flag = 0
453- (hasByte6 , flag ) = Convert .serializeSpace (memo .space )
454- if stream :
455- # Encoding: 0x20=custom, 0x21=0xFD, 0x22=0xFE, 0x23=0xFF
456- spaceFlag = 0x20 if hasByte6 else (flag | 0x20 )
457- else :
458- # Encoding: 0x00=custom, 0x01=0xFD, 0x02=0xFE, 0x03=0xFF
459- spaceFlag = 0x00 if hasByte6 else (flag | 0x00 )
487+ header = MemoryConfigurationHeader (memo .space )
488+ spaceFlag = (0x20 if stream else 0 ) | header .spaceIndex .value
460489 addr2 = ((memo .address >> 24 ) & 0xFF )
461490 addr3 = ((memo .address >> 16 ) & 0xFF )
462491 addr4 = ((memo .address >> 8 ) & 0xFF )
0 commit comments