...
Expand |
---|
During the generation of certain JSON messages some functions, in particular MJPrintf(), will be called recursively. The depth of the recursion is a function of the number of levels of object and array nesting in a generated JSON string. Implementors are encouraged to minimize nesting both to reduce stack utilization on constrained platforms and to flatten OKMs that helps minimize cloud overhead when processing OKMs. |
...
Reset
Expand | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
The porting layer requires access to read a system tick count and optional access to read and write a real time clock. System TickMost systems will already have and use a system tick count. It is usually an integer value that is incremented at a constant periodic rate, typically inside a timer interrupt. Unlike a real-time clock that usually has a resolution of 1 second, the system tick counter allows application code to time intervals down to the millisecond resolution. And unlike a real-time clock that has discontinuities when time is changed, the system tick counter always, predictably increases until the system is reset. The CWL library requires access to a system tick count. First, the constant CW_PORT_TICKS_PER_SECOND must be defined to indicate the number of system tick counts per second. Second the CWPortTick_t type must be defined to match the data type of the system tick counter. Third, the CWPortGetTick64() function must be implemented to return the current tick counter value in a synchronized manner. A system tick resolution of 10ms is acceptable, although a resolution of 1ms makes timing more precise and is especially beneficial for half-duplex serial communication to the CWM. By these guidelines, CW_PORT_TICKS_PER_SECOND should be roughly in the range of 100 to 1000. This constant should include a type qualifier such as ull for 64-bit system tick counters to ensure that arithmetic operations use the correct type. As implied by the name, CWPortGetTick64() function expects to return a 64-bit tick counter value, and this is the preferred implementation. When using a 64-bit tick counter, code can safely ignore the case when the timer wraps back to zero, since this would take a very, very long time to occur. Because integer overflow is not relevant, tick comparisons are straightforward and simplify the code logic. Most modern toolchains support the uint64_t type even on 8 and 16-bit microcontrollers. For platforms that support 64-bit integer types, but that currently use a 32-bit tick counter there are the following options: One, convert the system to a single 64-bit tick counter. Two, add a parallel 64-bit tick counter so that existing code using the 32-bit counter does not need to be updated. Three, use the 32-bit tick counter anyhow. For platforms that do not support 64-bit integer types or that decide not to use them the implementor may simply define CWPortTick_t to be a 32-bit integer type and CWPortGetTick64() will return a 32-bit value. The drawback is that tick counter integer overflow will occur regularly on the human time scale. For example, a 32-bit 10ms timer tick will wrap every 1.3 years, and a 1ms ticker tick will wrap every 49 days. When wrap occurs, there is the possibility that active timeout intervals within the CWL will malfunction. To avoid these potential malfunctions the system controller could reset itself before the timer tick count wraps. For example, the system controller could reset itself at 3AM if the timer tick count has been running for more than 45 days. Rebooting the system controller during off hours minimizes any impact to normal operation. CWPortGetTick64() must safely make a copy of the current tick counter and return it. For example, if the system tick counter is updated in a timer interrupt, then CWPortGetTick64() must turn off interrupts, make a copy of the system tick counter, turn on interrupts, and finally return the copy of the system tick counter. Not providing proper synchronization when accessing the system tick counter will invariably cause the CWL to malfunction in unexpected ways. MSP430 Pseudocode ExampleThis section has pseudocode taken from a working MSP430-based embedded platform that properly implements synchronized retrieval of a 64-bit system tick count. The tick counter is a uint64_t type. CWPortTick_t is defined to be a uint64_t type. The system tick fires 1024 times per second to increment the counter, so CW_PORT_TICKS_PER_SECOND is defined accordingly. The CWPortGetTick64() function makes a copy of the system tick counter while interrupts are disabled. This synchronizes it with the timer interrupt and prevents corrupted tick values from being read and returned to the CWL.
Clock MinuteThe CWL library aligns heartbeat reports to the top of every clock minute. The clock epoch, whether Jan 1, 1970, 2000, or some other date will not affect the proper operation of the library. The CWPortGetClockMinute() function must be implemented to return the current clock minute, a value from 0 to 59, and must properly synchronize access to the clock data source so that data corruption does not occur. If an OKE system does not have an RTC then the system must use the system tick counter to simulate a clock minute. MSP430 Pseudocode ExampleThis section has pseudocode taken from a working MSP430-based embedded platform that properly implements synchronized retrieval of the current clock minute. For context, this MSP430 platform uses a bare metal, single threaded, main execution loop. The RTC peripheral is only accessed in the context of the main loop, therefore no other conflicting access can occur simultaneously. Therefore, using an explicit synchronization primitive such as a critical section or semaphore is not required. The while loop waiting for the RTC to be ready is an access requirement of this specific RTC and will not be applicable to all RTCs. The _RTCBCD2Bin() function converts the BCD representation of the RTCMIN register into a 2's complement integer value. Accessing hardware RTC peripheral registers will vary between platforms.
ISO 8601 TimeISO 8601 defines a standard way to unambiguously convey dates and times as structured ASCII strings. The CWL only requires a small subset of the standard to be implemented so that current time can be read and written as an ISO 8601 string. A platform will typically not maintain time as an ISO 8601 string. Rather a platform will convert an ISO 8601 string into its local time representation or will convert its local time representation into an ISO 8601 string on an as needed basis. The minimum requirement for implementors is to be able to parse and generate an ISO 8601 string such as "2019-12-03T16:17:00". The string contains a date and time field separated by a capitol T. The date field is a 4-digit year, a minus sign, a 2-digit month (01-12), a minus sign, and a 2-digit day (01-31). The time field is a 2-digit hour (00-23), a colon, a 2-digit minute (00-59), a colon, and a 2-digit second (00-59). When no time zone information is explicitly indicated, the default, the OKE will treat it as local time. Local TimeThe OKC knows the correct local time, accounting for both time zone and daylight savings, of a connected OKE. When time is written to the OKE, local time will be set, and time-zone context will not be provided by default. This means that OKE can display the correct local time, but without time zone context. UTC aligned time is not written to connected devices since this would require connected devices to also be properly configured for time zone and daylight savings to perform local time calculations competently. Only setting local time to the connected devices greatly simplifies ensuring correct time is displayed and used by the OKE. The CWM periodically writes local time as an ISO 8601 string to the OKE. The CWM gets current local time either directly from the OCK or indirectly via the IoT Gateway which gets time from the OKC. If a platform currently displays or allows manual configuration of current time, time zone and/or daylight savings then some adjustments must occur. First, when using the OKC manual time configuration should not be permitted since OKC expects to and will automatically set and maintain the proper clock time through the CWM. Second, when using the OKC, displayed time must not also report a time zone or daylight savings context since that information is not conveyed to the OKE. Reading ISO Timeint8_t* CWPortGetISOTimeStr(void); The CWPortGetISOTimeStr() function must return a pointer to a persistent, valid, NULL terminated ISO 8601 time string. The time string must not include time zone information and should report local time. Implementors may include sub-second precision only if application specific requirements warrant it. For example, a typical time string value would be: "2019-02-03T16:59:01". The string may not allocated from the stack of the CWPortGetISOTimeStr() function because the time string must persist after the function returns. Therefore, the time string should be allocated as a global variable or statically within the scope of the function. The function must also synchronize access to the RTC so that data read from it is not corrupted. If an OKE system does not have an RTC, then it must return the string "0000-00-00T00:00:00". When the CWM receives a message from an OKE system with a "0000-00-00T00:00:00" timestamp it will update the timestamp with its current time before sending the message to the OKC. Millisecond or Microsecond ISO Time ResolutionNearly all OKE implementations DO NOT require sub-second time stamping. The CWM automatically ensures that the exact sequence of messages generated by the OKE is preserved and no data will be overwritten when it is transferred to and stored by the OKC even when some of those messages have the same timestamp and the same data fields. If there is a use case for sub-second resolution time stamps, the OKE implementor will need to perform additional implementation specific steps to queue and forward the messages via the CWPortGenHeartbeatCB() callback and override the CWL's default timestamp. Another possible solution to high resolution timestamping is to curate the data into a log file containing the desired timestamp resolution and use the CLW's file transfer to write the log to the cloud for processing. In either case, please consult with the OKC team before designing and implementing high resolution timestamps since it will require additional OKC development to appropriately process the application specific data. Writing ISO Timevoid CWPortSetISOTime(int8_t *isoTimeStr) ; The CWPortSetISOTime() function takes a valid ISO 8601 time string and updates its internal clock time. Implementors only need to handle and process the most basic of ISO 8601 time strings, ones of the form "yyyy-mm-ddThh:mm:ss", and must ignore any unhandled time string formats. For example, time string formats including time zone information must be ignored if the implementation only handles the basic format. When processing a basic time string without any time zone information, the function must interpret it as the current local time of the OKE. The function must also synchronize access to the RTC so that data written to it is not corrupted. If an OKE system does not have an RTC, then it may ignore the set time request. ISO 8601 Platform SupportSome platforms may natively support ISO 8601 time strings. Those platforms may utilize the native interfaces to implement the CWL requirements. For platforms that do not natively support ISO 8601 time strings the example MSP430 pseudocode in the next section shows how to implement the CWL requirements using standard C library functions. MSP430 Pseudocode ExamplesThis section has pseudocode taken from a working MSP430-based embedded platform that properly implements synchronized reading and writing of ISO 8601 time using only standard C library functions. This platform only supports the basic form of the ISO 8601 time string, which is always 20 bytes in length including the NULL terminator. In the CWPortGetISOTimeStr() function the buffer into which the current local time is formatted is declared as static. This ensures the buffer persists after the function returns. The ReadCurrentTimeAndDate() function is a platform specific function that reads the current time and date into easy to use structure. Because this platform operates as a single main execution loop and the RTC is never accessed from an interrupt context it is not necessary to explicitly synchronize access to the RTC using critical sections or a semaphore. A snprintf() call is used to generate the current local time as a valid ISO 8601 string. Because snprintf() does not guarantee NULL termination, a NULL terminator is explicitly set. The pointer to the newly formatted, NULL terminated buffer is returned. The CWPortSetISOTime() function is passed what should be an ISO 8601 time string. Since this implementation only support the basic form of ISO 8601 strings the size of the input string must be exactly 20 bytes, and the field separators must be in the correct positions. If the length and field separators are valid then the code attempts to convert the fields into integer values written to a TimeAndDate structure. The TimeAndDate structure is platform specific and is not a standard time structure. After the string to integer conversions are complete the code sanity checks the ranges of the date and time fields. Only if the sanity check passes will the code write the time to the RTC. Although this code deliberately ignores other forms of ISO 8601 time strings which may include time zone information or additional precision it meets CWL requirements.
|
Reset
Expand | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
The CWPortExeReset() function must force a full warm start of the OKE controller. The function must ensure that the serial transmit software and hardware buffers are empty before resetting. This function should not return to the caller. It is strongly recommended that the reset function call the CW_ERROR() macro to force the reset. If the assertion handler is implemented to log the assertion, then the cause of the reset will be traceable to a deliberately commanded action rather than a spurious problem. MSP430 Pseudocode ExampleIn this example port to an MSP430 platform, the CWPortExeReset() function first waits for the transmit software queue to empty, then waits 10ms for the hardware buffers to finish transmitting on the wire. Finally, an assertion is forced. The assertion handler ensures the file and line number is logged and then performs a warm reset.
|
Flush
Expand | |||||
---|---|---|---|---|---|
The CWPortExeFlush() function is intended to permit the OKC to clear out persistent/non-volatile data such as accumulated logs or other data that could backlog or inhibit the desired operation of the equipment. Primarily this function applies to the CWM that has very deep non-volatile queues, that in some cases the OKC needs to empty. For many OKE implementations this function may not take any action. |
Versions
Expand | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
The CWPortGetVersion() function returns platform type, software/firmware version, and hardware revision information about the OKE. The function allows the CWL to sequentially read zero or more sets of version information about the platform so that it can be reported to the OKC. This means it is possible for a controller running the CWL to report versions for multiple components of the system. This is particularly important so the OKC can determine if firmware updates must be applied. Even when a component is not in-system upgradeable, having an inventory of the versions is critical for remote asset management. So, when in doubt report the version information of as much of the OKE system as possible. The CWL reports version information to the OKC automatically, and calls the CWPortGetVersion() to fetch the versions. The CWL will call the CWPortGetVersion() in a loop initially passing an index of 0. If there is version information for index 0, which should always be the case, then the CWPortGetVersion() will set the parameter pointers to static strings for the type, firmware, and hardware NULL terminated strings, and then return true. It is important that the string buffers are not on the stack frame of the CWPortGetVersion() function, since they must be accessible after the function returns. Next the CWL will invoke CWPortGetVersion() again with an index of 1. If there are additional versions to report then the pointers will be updated and the function will return true, then the CWL will then ask for index 2. Else the function will return false and the CWL will know that no more version information is available. When CWPortGetVersion() returns true, all of the pointers must point to a valid NULL terminated string. The type, software/firmware version, and hardware revisions are NULL terminated strings. The CWL does not impose any restrictions on the format of any of the version strings. However, it is recommended that implementors consider how the version information will be handled by the OKC. The goal is for the OKC to reliably detect what version is running and initiate configured firmware updates. This means the type and firmware version strings should be unambiguous and consistent across versions. In some cases, the hardware revision of a component may not be known, in this case a pointer to an empty string "" should be returned. Pseudocode ExampleIn this example code the CWPortGetVersion() function returns two sets of version information. The first time the CWL calls the function with an index of 0, *typeStr is set to point to "TCHEF", *fwverStr is set to point to "1.3.42", and hwrevStr is set to point to "A1", and true is returned. The second time the CWL calls the function with an index of 1, *typeStr is set to point to "TCAB", *fwverStr is set to point to "4.2.c1", and hwrevStr is set to point to "REV3", and true is returned. The last time the CWL calls the function it returns false.
|
Heartbeats
Expand | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
OKE systems must send periodic OKMs that contain the operational status of the system. The CWL will automatically invoke CWPortGenHeartbeatCB() once every minute to allow the application code to generate its application specific heartbeat message. The heartbeat message must be valid JSON. CWPortGenHeartbeatCB() is an MJSON printer function invoked under the context of a MJPrintf() call triggered by the library. Implementors must use MJPrintf() calls within the context of CWPortGenHeartbeatCB()to generate the heartbeat message. The function must return the total number of new bytes written to the message. Implementors will have either 300 or 800 (default) bytes of buffer space for generating each heartbeat message depending on the CWL configuration. Implementors can cause the callback to be invoked additional times when more than one heartbeat OKM must be generated each minute or when a critical state change occurs. This is done by calling CWCmdScheduleHeartbeat(). The last heartbeat message per minute interval should not invoke CWCmdScheduleHeartbeat() because that will cause continuous heartbeat generation. Heartbeat ThrottlingThe CWL has added a throttling mechanism for heartbeats that prevents the generation of more than CW_PORT_MAX_HEARTBEATS_PER_MIN per minute. Continuous generation of heartbeat messages clogs up the outbound store and forward queues and the bandwidth to the OKC with highly redundant information. Additionally, it can starve out other CWL message generation. CW_PORT_MAX_HEARTBEATS_PER_MIN is defined in cwport.h and the default is 15 which should be more than enough for most applications such that they are never throttled. If a throttling event occurs the CWL will report additional diagnostics to the OKC so that the condition is recorded. When the next one-minute period begins, the application will be allowed to send another set of heartbeats. Common causes of excessive heartbeat generation includes calling CWCmdScheduleHeartbeat() every time CWPortGenHeartbeatCB() is invoked and calling CWCmdScheduleHeartbeat()continuously while the equipment is in a fault state when the desired behavior is to send a single set of heartbeats after entering the fault state. Partitioning HeartbeatsThere is a 1000 total byte limit for each OKM including required overhead added by the CWL and NULL terminator. Implementors must target a maximum heartbeat payload size of either 300 or 800 bytes. This will adequately account for overhead and some margin for future protocol changes. Many systems will be able to encode all the required heartbeat data in 800 bytes. However, some systems may have more than 800 bytes of heartbeat data, and/or may be organized in such a way that logically organizing data in distinct heartbeats is convenient. For example, a modular oven system, may send a separate heartbeat for each cabinet. From an efficiency perspective it is better to have fewer, larger heartbeats, rather than many, smaller heartbeats, since the overhead of each message is constant. OKM FormattingIt is valid for JSON strings to include whitespace and line breaks. This formatting does not alter the meaning of a JSON message. However, it does unnecessarily consume additional memory and network bandwidth. Therefore, implementors should not include whitespace or line breaks in their generated messages. Rate LimitingHeartbeats should only be generated once per minute. It is best to report faults and other events that occur between heartbeats as ascending counters. These counters can be reset to 0 on a restart. The OKC can determine the type and number of faults that occurred between two heartbeats. The OKE may generate additional heartbeats when certain events occur, but this is not encouraged. However, if a requirement exists to do so then the OKE must implement a rate limiting mechanism to prevent the CWM store and forward queue from being filled with OKMs that consume storage and network resources and limit the ability to diagnose problems by delaying delivery of newer OKMs to the OKC. Every OKE must be a good OpenKitchen citizen and try to use the minimum resources necessary when sending heartbeats. By doing so the entire OpenKitchen system will work better for our customers. Pseudocode ExampleThis example is from the Window port. It shows a simple scenario where the application code generates two heartbeat message each minute. This is a contrived example since all the data could have fit into a single heartbeat. When hbState is zero the callback prints the door count data, requests an additional heartbeat by calling CWCmdScheduleHeartbeat() and changes hbState to 1. The CWL will invoke the callback a second time after the first one is sent. Since hbState is one the temp data is reported, and the state is set back to 0. It does not call CWMsgTxHeartbeat() again since all the heartbeats have been sent for this cycle.
The following are messages generated by the example code.
The CWPortGenHeartbeatCB() function only is responsible for the "door":8 and "temp":359 portions of the messages. The rest of the JSON is automatically generated by the library. |
File Transfer
Expand | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
File Transfer | ||||||||||
| CWM | |||||||||
OKE Connection | MiWi | WiFi | ||||||||
Full Duplex Serial (TTL/RS-232) | <~2000bps | Legacy Firmware <= 140 <~OKE Baud Rate/4 (9600bps – 230400bps)/4
Latest WiFi CWM FW >= 141 115200bps – ~0.34MB/minute 230400bps – ~0.58MB/minute 250000bps – ~0.62MB/minute 500000bps – ~1.0MB/minute 1000000bps – ~1.2MB/minute | ||||||||
Half Duplex Serial (RS-485) | <~2000bps | <~OKE Baud Rate/4 (9600bps – 230400bps)/4 | ||||||||
USB (Device or Host) | <~2000bps | Legacy Firmware <= 140 <~230400bps/4
Latest WiFi CWM FW >= 141 ~1.3MB/minute |
Info |
---|
If using CWCRC16 function to calculate CRC of a menu file, you should use the CWCRC16_INITIAL_VALUE constant defined as 0xffff in cwcrc16.h as the initial Crc Value because that is the seed that OKC uses to generate CRC uint16_t CWCRC16(const uint8_t *bufptr, uint16_t len, uint16_t initialCrcValue); initialCrcValue should be CWCRC16_INITIAL_VALUE when calling CWCRC16. |
A file version to differentiate between different file encodings. This ensures files with incompatible encodings are safely ignored by the OKE.
File Transfer Status
When the OKC is writing a file, an OKE system will report on the status of the write operation. This allows the OKC to determine if the file transfer is ongoing, stalled, or completed. If the file transfer appears stalled, for example only half the file was downloaded due to a network connectivity problem. Then the OKC can restart the file transfer from where it left off without starting from the beginning. This can be very helpful when attempting to transfer large files in challenging network conditions. The CWL automatically sends File Transfer Status updates to the OKC.
File Status
Independent of reporting the current status of a file being written to the OKE, the names of important individual files maintained by the OKE are also reported automatically by the CWL.
An OKE system must be targeted in its list of reported files, for example it should NOT try to report every single file in a Linux root file system. Besides being too large to fit into a single OKM it would consume tremendous bandwidth to report on files that are of little direct management value.
Some examples of files that might make sense to report are the name of recipe configurations, or the name of diagnostic logs. It is not necessary to list the names of firmware files since they are enumerated in the version data. The idea is to enable the equipment to be effectively managed by the OKC. Implementors should think about the capabilities of the product and the use cases for its utilization. Based on this knowledge, provide file information to the OKC that can enhance it manageability.
Firmware
Many systems that implement file transfer will also implement in-system firmware updates. The OKC will transfer firmware images to an OKE system via a file write. An OKE system will be able to write the new firmware image to secondary storage. If the file transfer is successfully and the firmware passes platform specific integrity checks then it should be automatically installed.
Writing from the OKC to an OKE System
Code Block | ||
---|---|---|
| ||
bool CWPortGetInboundFileWriteBuf(uint8_t** buf, uint16_t* bufSize);
bool CWPortExeFileWrite(int8_t* fileStr, uint32_t offset, uint8_t* data, uint16_t dataLen, int32_t fileSize) ; |
The CWPortGetInboundFileWriteBuf() function returns the buffer into which file fragment data being written to an OKE system is temporarily stored by the CWL. The function must allocate a buffer of at least CWCMD_FILE_FRAG_SIZE bytes. It will pass a pointer and the allocated size back to the CWL via the buf and bufSize pointers then return true. The function must return false if the buffer could not be allocated. Because only one fragment buffer is used by the CWL at a time the buffer may simply be a global or statically defined buffer. The buffer is only used by the CWL from the time the CWPortGetInboundFileWriteBuf() function returns until the CWPortExeFileWrite() function finishes writing the data buffer to non-volatile memory. Therefore, implementors may also allocate the buffer once, dynamically from heap memory, and then free it when the file transfer completes.
The CWPortExeFileWrite() function is called every time a new, in sequence, file fragment being written to an OKE system is received. The fileStr parameter is a NULL terminated string containing the name of the file being written. The file name will remain constant for the duration of the file transfer. The offset parameter indicates the byte offset from the start of the file where the new file fragment should be written. When offset is zero this means a new file transfer is being started, any previous open or incomplete file transfer must first be aborted and then the new file transfer opened. The data parameter points to the binary data that should be written to the file, and it is the buffer returned by CWPortGetInboundFileWriteBuf(). The dataLen parameter is the number of bytes of data to be written. If dataLen is equal to CWCMD_FILE_FRAG_SIZE, then the file transfer is not yet complete. If dataLen is less than CWCMD_FILE_FRAG_SIZE bytes then it is the last fragment of file data, and the file can be closed after writing the last fragment. If the file is an even multiple of CWCMD_FILE_FRAG_SIZE bytes then the last fragment will have a dataLen of zero and the file can be closed without writing any additional data. When known fileSize indicates the total size of the file in bytes, otherwise it will be -1. fileSize must only be used by implementors to indicate file download progress, it must not be used to detect the end of the file transfer.
The CWL simplifies the logic required in CWPortExeFileWrite(). First the CWL only permits one file to be written to an OKE system at a time. Therefore CWPortExeFileWrite() must only manage one open file at a time. Second, CWPortExeFileWrite() will only be called with the next logical file fragment offset, or with an offset of zero if a new file transfer if requested. Therefore, the CWPortExeFileWrite() function does not need to be concerned with out of sequence file fragments.
The CWPortExeFileWrite() must return true if it successfully processed the file fragment. If an unexpected error occurs, then the function must return false.
Throttling File Fragments
As previously discussed, the CWM buffers all file fragments from the OKC. The OKE can determine the pace at which the file fragments are received in case its local non-volatile memory cannot be written to fast enough. This section describes two strategies for the OKE to implement throttling.
Simply block in the CWPortExeFileWrite() function until the file fragment data can be written. Implementations can block for up to 5 seconds before any serial bus timeouts with the CWM will occur. Typically, most platforms would not need to block for more than 10-100ms. The drawback to this approach is most apparent in bare metal systems that run from a single main loop. Blocking in CWPortExeFileWrite() will block the main loop from being serviced. Implementors will need to determine if the additional latency will adversely affect the system operation or not. For RTOS, or Linux implementations that have a dedicated CWL task/thread, blocking in CWPortExeFileWrite() should not pose any problems.
Modify the bare metal main loop, or the CWPortTask() so that it does not call CWMsgTask() if the non-volatile memory is not ready to write data to it. Functionally this is like #1, except that in the case of a bare metal system other functions of the main loop can still be serviced.
Implementors may also consider lazy blocking. For example, after writing data to a non-volatile memory the memory usually goes into a busy state for a period during which it cannot accept new writes. Instead of writing and immediately blocking waiting for the write to complete the CWPortExeFileWrite() function should return without blocking. The next time the CWPortExeFileWrite() function is called it can block conditionally only if the write operation is still in progress. Deferring the memory busy check can increase file throughput considerably.
Pseudocode Example
This example code is from the Windows port of the CWL.
The CWPortGetInboundFileWriteBuf() function simply returns a static buffer pointer of the required minimum size. The CWL uses this buffer to pass the file fragment data to the CWPortExeFileWrite() function as the data pointer.
The CWPortExeFileWrite() function first checks to see of the offset is zero. If zero it means a new file transfer is being started. If a previous file was opened it is first closed, then the new file is opened. The data is then written to the file. You will notice that the offset is not used in the fwrite() function call. This is because the standard C file functions keep track of the current file pointer. Since the CWL only calls the CWPortExeFileWrite() function with sequential file fragments the code can simply write the fragment out to the file without being concerned that the file will get mangled. Finally, the file is closed if the dataLen is less than CWCMD_FILE_FRAG_SIZE since that indicates the end of the file transfer.
A real implementation may need to look at fileStr to determine the type of file, where and how to store it, and what if any actions to take after the file is successfully written.
Code Block | ||
---|---|---|
| ||
Bool CWPortGetInboundFileWriteBuf (uint8_t** buf, uint16_t *bufSize) {
static uint8_t ftBuf[CWMSGCMD_FT_MAX_SIZE];
CW_REQUIRE(buf != NULL);
CW_REQUIRE(bufSize != NULL);
*buf = ftBuf;
*bufSize = sizeof(ftBuf);
CW_ENSURE(*buf != NULL);
CW_ENSURE(*bufSize >= CWMSGCMD_FT_MAX_SIZE);
return true;
}
bool CWPortExeFileWrite (int8_t* fileStr, uint32_t offset, uint8_t* data, uint16_t dataLen) {
CW_REQUIRE(fileStr != NULL);
CW_REQUIRE(data != NULL);
CW_REQUIRE(dataLen <= CWMSGCMD_FT_MAX_SIZE);
if (offset == 0) {
if (fpWrite != NULL) fclose(fpWrite); /* Close open files */
CW_ASSERT((fpWrite = _fsopen(fileStr, "wb+", _SH_DENYNO)) != NULL);
}
CW_ASSERT(fwrite(data, 1, dataLen, fpWrite) == dataLen);
if ((dataLen < CWMSGCMD_FT_MAX_SIZE) && (fpWrite != NULL))
{ fclose(fpWrite);}
return true;
} |
Common Problems
OKE implementation expects a hardcoded file name and file uploaded to OKC doesn’t match:
It’s commonly seen that developers hard code an expected file name for certain file types, e.g. firmware.bin, menu.json. If someone subsequently uploads a file to the OKC with a different name (e.g. firmwareV1-2-3.bin), the file will be downloaded to the OKE (and indeed the status shown in Open Kitchen will be ‘downloaded’), but it isn’t loaded into the system because of the name mismatch. We recommend avoiding this approach and to instead encode identifiable information in the file header. If not avoidable, then ensure the file uploaded to the OKC is correctly named
Old CWM firmware and/or CWL version: In CWM1 firmware earlier than PIC154/SPH145 and ConnectWare Library versions < 1.0.15, there was a bug in the fragmentation layer that caused messages of certain lengths to be dropped. This can cause a multitude of issues including download rewinds due to dropped fragments (ultimately ended in an aborted download), and files that do ‘successfully' download to the CWM but aren’t loaded into the OKE. If you encounter such issues, please ensure your CWM firmware and CWL versions are up to date.
File name + extension longer than 16 characters: the OKC now prevents this from happening, but it may still be possible to do with the simulators. The CWL has a 16 character limit on the file name including the extension. If this limit is exceeded, the CWM will download the fragments from the OKC but just throw them away. Please ensure file names are <= 16 characters including extension.
Code Block | ||
---|---|---|
| ||
void CWPortExeReset(void); |
The CWPortExeReset() function must force a full warm start of the OKE controller. The function must ensure that the serial transmit software and hardware buffers are empty before resetting. This function should not return to the caller.
It is strongly recommended that the reset function call the CW_ERROR() macro to force the reset. If the assertion handler is implemented to log the assertion, then the cause of the reset will be traceable to a deliberately commanded action rather than a spurious problem.
MSP430 Pseudocode Example
In this example port to an MSP430 platform, the CWPortExeReset() function first waits for the transmit software queue to empty, then waits 10ms for the hardware buffers to finish transmitting on the wire. Finally, an assertion is forced. The assertion handler ensures the file and line number is logged and then performs a warm reset.
Code Block | ||
---|---|---|
| ||
Void CWPortExeReset(void)
{
while (TxDataQueueIsEmpty() == false);
DelayMs(10);
CW_ERROR();
} |
Flush
Expand | |||||
---|---|---|---|---|---|
The CWPortExeFlush() function is intended to permit the OKC to clear out persistent/non-volatile data such as accumulated logs or other data that could backlog or inhibit the desired operation of the equipment. Primarily this function applies to the CWM that has very deep non-volatile queues, that in some cases the OKC needs to empty. For many OKE implementations this function may not take any action. |
Versions
Expand | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
The CWPortGetVersion() function returns platform type, software/firmware version, and hardware revision information about the OKE. The function allows the CWL to sequentially read zero or more sets of version information about the platform so that it can be reported to the OKC. This means it is possible for a controller running the CWL to report versions for multiple components of the system. This is particularly important so the OKC can determine if firmware updates must be applied. Even when a component is not in-system upgradeable, having an inventory of the versions is critical for remote asset management. So, when in doubt report the version information of as much of the OKE system as possible. The CWL reports version information to the OKC automatically, and calls the CWPortGetVersion() to fetch the versions. The CWL will call the CWPortGetVersion() in a loop initially passing an index of 0. If there is version information for index 0, which should always be the case, then the CWPortGetVersion() will set the parameter pointers to static strings for the type, firmware, and hardware NULL terminated strings, and then return true. It is important that the string buffers are not on the stack frame of the CWPortGetVersion() function, since they must be accessible after the function returns. Next the CWL will invoke CWPortGetVersion() again with an index of 1. If there are additional versions to report then the pointers will be updated and the function will return true, then the CWL will then ask for index 2. Else the function will return false and the CWL will know that no more version information is available. When CWPortGetVersion() returns true, all of the pointers must point to a valid NULL terminated string. The type, software/firmware version, and hardware revisions are NULL terminated strings. The CWL does not impose any restrictions on the format of any of the version strings. However, it is recommended that implementors consider how the version information will be handled by the OKC. The goal is for the OKC to reliably detect what version is running and initiate configured firmware updates. This means the type and firmware version strings should be unambiguous and consistent across versions. In some cases, the hardware revision of a component may not be known, in this case a pointer to an empty string "" should be returned. Pseudocode ExampleIn this example code the CWPortGetVersion() function returns two sets of version information. The first time the CWL calls the function with an index of 0, *typeStr is set to point to "TCHEF", *fwverStr is set to point to "1.3.42", and hwrevStr is set to point to "A1", and true is returned. The second time the CWL calls the function with an index of 1, *typeStr is set to point to "TCAB", *fwverStr is set to point to "4.2.c1", and hwrevStr is set to point to "REV3", and true is returned. The last time the CWL calls the function it returns false.
|
...