SensoredHacker's On The Line Challange with PLCs, HMIs, Arduinos
R&D from May through June 2026
The original FC6A library began after a work project where I needed to log basically every PLC event in a testing array. I wrote the functions I needed, finished the project, and then thought: hey, wouldn't it be neat to reuse some of this code? So I wrote fc6a.py, used it for a while, and built a collection of loggers, monitors, plotting tools, and other utilities around it.
From there I kept experimenting with IDEC Maintenance Protocol on Linux PCs, Android phones, ESP32 devices, Arduinos, and whatever else I could reasonably connect to a PLC. One of my logging tools got some attention, someone asked for a multi-PLC serial logger, and that turned into more protocol work. During the entire process I have been desperately looking for all available documentation. There is some documentation. There is not enough documentation. And the quality varies wildly.
When I applied to Element14's On The Line Design Challenge, I already had fc6a.py and the beginnings of MiSmSerial.py. What I did not appreciate yet was how much the project was going to expand.
Repository: https://github.com/Makerspace-Bangor/fc6a
On The Line work: https://github.com/Makerspace-Bangor/OnTheLine
About PLCs
Programmable Logic Controllers (PLCs) are industry-standard devices designed primarily for controlling industrial machinery and processes. I sometimes describe them as proprietary, industrial-grade Arduinos. That is obviously an oversimplification, but it gives people who have never worked with PLCs a reasonable place to start.
PLCs are not general-purpose computers, and they often rely on programming methods that can look obscure or archaic to someone coming from C, Python, or normal embedded development. PLCs first appeared commercially around 1969, and ladder logic still represents control systems in a way derived from relay diagrams that are much older than the computer itself.
PLCs are complex in their simplicity. The basic concepts are remarkably consistent, but every OEM implements its own collection of communications, register maps, programming software, special functions, and terminology. Once a machine is built around one ecosystem, that ecosystem can become expensive to leave.
This project explores what happens when some of those boundaries are treated as engineering problems instead of fixed limitations. The immediate target is IDEC Maintenance Protocol, but the larger idea is interoperability: let PLCs, HMIs, PCs, and microcontrollers exchange useful control data without requiring every device to come from the same product family.
Nomenclature
Every OEM seems to have its own terminology. Terms such as DiSM, MiSM, DM Link, "maintenance communication," and similar names may refer to related ideas while describing different implementations. That gets confusing very quickly. For this article I am going to use Maintenance Protocol as the general term unless the distinction matters.
Registers / addresses: PLC applications are organized around device addresses such as D registers, M relays, inputs, outputs, timers, and counters. For this project I sometimes use "register" loosely to mean an addressable value exposed to another device.
Endian: PLC documentation may describe byte packing, word order, upper/lower byte storage, or 32-bit storage settings. I will generally call all of that endian or data ordering unless a specific distinction matters.
Register map: where specific data is stored. Register maps are different between OEMs and often different between projects. Most of my examples are IDEC because I have a lot of IDEC PLCs and use them most often.
What I thought I was going to build
The original plan was pretty simple:
- Implement Maintenance Protocol as a server on an Arduino.
- Send and receive Maintenance Protocol packets.
- Expose useful PLC-style registers from Arduino hardware.
- Map I2C, SPI, CAN, serial devices, GPIO, and sensors into those registers.
- Eventually create a reusable Arduino library for PLC controls.
That was the plan, anyway.
The first obvious problem: memory
My first instinct was to make a big ol table of registers. D0000-D9999 as 16-bit words alone is about 20 KB. Add a comparable M space, bookkeeping, networking, protocol parsing, buffers, and the actual application, and a small MCU gets crowded very quickly.
One early test with effectively one configured register compiled like this:
Sketch uses 15300 bytes (6%) of program storage space. Maximum is 253952 bytes.
Global variables use 1761 bytes (21%) of dynamic memory, leaving 6431 bytes for local variables.
Maximum is 8192 bytes.
I could get creative and compress things, but the more important realization was that I probably should not allocate the entire PLC address space at all. A real project normally uses a sparse subset of the available addresses. If an HMI asks for D0100, I do not need 9,999 other D registers sitting in RAM just to answer it.
That eventually became one of the main architectural ideas of the project: the register does not have to be memory; the register can be an interface.
The Arduino Uno Q experiment
Element14's On The Line Design Challenge provided me with an Arduino Uno Q to build this project around. I am genuinely grateful for the opportunity to test that hardware, but for this particular project it did not go well for me.
I tried using the Uno Q with an Ethernet shield. I fixed problems I ran into with the Ethernet library and posted those fixes to GitHub. I had hoped the additional resources available on the Uno Q would make it a better host for a fairly demanding PLC/HMI networking experiment.
The limitation was not just "can I fit the program." For this application I also needed reasonably predictable network response timing. When the device is pretending to be a PLC, the client expects a reply, and delays eventually become disconnects and communication faults. I could not get the realtime-ish behavior I wanted from the Uno Q implementation. I kept seeing timing issues and frequent disconnects.
That does not mean the Uno Q is a bad board. It means it was not a good fit for the way I was trying to implement this particular server.
So I looked at other hardware. The Arduino Mega gave me more room to write intentionally inefficient experimental code and figure out what the protocol needed before optimizing it. I also tested a Teensy 4.1, including expanded PSRAM and native Ethernet, but I use my Teensy for other work and did not want to permanently dedicate it to this project. I also spent time with the Uno Minima form factor because it is convenient for this kind of bench work.
[Compiling Uno Q / Ethernet test setup]
Source: UnoQ_installing.png
The simpler idea: the PLC wants a register, so give it a register
This is the simplest idea in the whole project. The PLC or HMI wants a 16-bit register? That is what we give it.
From observing Maintenance Protocol in action, I like to say that the only thing the PLC knows about is registers. Perhaps that is not expressly true, but it is close enough to be a useful design model.
For any given sensor, on any given bus, the Arduino can supply the PLC or HMI with the datatype it requested. The address becomes an abstraction over whatever is actually behind it.
I2C sensor --------\
SPI device ---------\
CAN device ----------> Arduino ---> virtual PLC registers ---> HMI
GPIO ---------------/
Serial device -----/
For example:
D0100 = Temperature
D0101 = Pressure
D0102 = RPM
M0100 = Motor Running
M0101 = Alarm
M0102 = Door Switch
The HMI does not need to know that D0100 came from I2C, D0101 came over CAN, and M0102 is a physical pin. It asks for addresses. The Arduino translates those addresses into useful data.
How Maintenance Protocol works
At its core, classic Maintenance Protocol is a request/reply protocol. The client initiates communication, the PLC processes the request, and the PLC returns a reply. That distinction matters later when we start making Arduinos and HMIs impersonate different sides of the connection.
Request framing
ENQ | DEVICE | CONT | CMD | TYPE | DATA | BCC | CR
05h | FF | 0 | R | D | ... | XX | 0Dh
| Field | Meaning |
|---|---|
| ENQ | 0x05. Starts a request. |
| Device | 00-1F for addressed devices; FF is commonly used for 1:1 communication. |
| Continuation | Normally 0. Some multi-message operations use 1 to indicate another message follows. |
| Command | Read, Write, Clear, and other command families. |
| Type | D registers, M relays, inputs, outputs, timers, counters, status functions, etc. |
| Data | Address, byte count, values, or command-specific parameters. |
| BCC | Block Check Character calculated using XOR over the applicable message bytes. |
| CR | 0x0D carriage-return terminator. |
Reply framing
ACK/NAK | DEVICE | RESULT/DATA | BCC | CR
A normal successful reply begins with ACK (0x06). A communication-level failure can return NAK (0x15). Some ACK replies can also contain an execution error code, so "ACK" does not always mean the requested operation was logically valid.
That sounds simple, and for basic reads and writes it is simple. The interesting part begins when you discover how many things the IDEC software suite can ask the devices to do.
Protocol reference: fc4a_protocol_im.pdf
From fc6a.py to MiSmSerial
The original fc6a.py library worked, but it made assumptions about how I thought the protocol worked at the time. More testing showed behavior that did not fit those assumptions.
MiSmSerial was my attempt to clean that up instead of endlessly patching the original library. Around the start of May 2026 I had expanded it well beyond basic bit/word/float operations. The newer work included inputs, outputs, timers, counters, multi-register operations, raw replies, better connection handling, and actual status monitoring. Which is to say: it had some status handling, where the original library basically had none.
I had also used Maintenance Protocol earlier with an ESP32 LoRa module to create a long-range PLC remote control. That experiment worked, but it also reinforced an important point: Maintenance Protocol was designed for trusted industrial networks. Putting the PLC directly on an untrusted network is a bad idea. More on that later.
MiSmTCP: same protocol, different transport
Once MiSmSerial was working well enough, I needed a new Ethernet implementation. I did not want to repeat the mistakes of fc6a.py, so I built MiSmTCP from the behavior I had already validated in MiSmSerial.
It was not especially difficult, because the important realization is:
The protocol does not really care whether the bytes arrived over serial or a TCP socket.
The transport layer changes. The Maintenance Protocol model does not.
So I began deliberately unifying the public language of the two libraries. An application should be able to think in terms of PLC operations instead of thinking "serial version of this function" and "TCP version of that function."
# conceptually the same application-level operation
plc.read("D0100")
plc.write("D0100", 1234)
plc.read_bit("M0100")
plc.write_bit("M0100", 1)
plc.read_float("D0200")
plc.write_float("D0200", 1.23)
That sounds like a small cleanup, but I think it is one of the more important improvements to the project. MiSmSerial and MiSmTCP stopped being two unrelated PLC libraries. They became two transports for the same protocol.
Then the HMI ruined my nice simple plan
In a normal PLC/HMI setup, the PLC is acting as the server and the HMI is the client. The HMI requests the values it wants and the PLC answers.
I wanted the Arduino to take the PLC's place, at least well enough that an existing HMI project could communicate with it.
My first assumption was that if my custom clients accepted the Arduino server, the HMI probably would too. Nope.
The Arduino Mega server could answer my own Maintenance Protocol clients, but the HG-series HMI was not having it. At one point the HMI was giving me:
Communication error READID:00h 0
No reply from Ext device
Confirm PLC com cable settings
I chased several possible explanations. Specific device addressing? Special status bits? IDEC MAC addresses? Some undocumented initialization value?
Wrong lead #1: maybe it needs an IDEC MAC address
I changed the Arduino MAC address to use an IDEC-looking prefix. This had no useful effect.
Wrong lead #2: the documented HMI initialization method
The documentation describes HMI startup in terms of populating particular PLC data, including time/status-related values. On one HMI program that appeared to work. On another program it did not. Then another program used apparently similar settings and behaved differently again.
So for a while I thought I just had the wrong magic register.
This was wrong, false, LIES!
Well, more precisely: those documented registers can be part of a particular application, but they were not the general explanation for what I was seeing.
2537 is not 2101
One very useful distinction eventually became obvious in the HMI project configuration. The HMI has its own maintenance service, commonly configured on port 2537 in the programs I tested. Separately, the HMI's External Device Communication driver connects to the FC6A-style device on port 2101.
Those are two different conversations. Confusing them caused several early false leads.
| Connection | Observed role in my projects |
|---|---|
| TCP 2101 | PLC-style Maintenance Protocol / external-device communication used by the HMI. |
| TCP 2537 | HMI maintenance/service interface used by IDEC tooling and HMI maintenance operations. |
That separation also helped explain why a device could be perfectly happy answering one kind of request and still fail another initialization workflow.
Stop guessing: ask the HMI what it wants
My initial workflow was Wireshark. I captured the HMI traffic, looked for register requests, added those registers to the fake PLC, tested again, and repeated.
This worked. It was not efficient.
I got sick of that pretty quickly, so I wrote an Arduino HMI Maintenance Protocol listener. The Arduino accepts the HMI connection and reports the addresses the HMI is actually requesting.
That meant I could do something as crude as:
tail -f /dev/ttyACM0 >> out.txt
Then click around the HMI and collect the register traffic. The Arduino listener is unrefined. It works, uses very little program space, and it is absolutely not a sophisticated parser. That was fine. Its job was to tell me what the HMI was asking for.
Arduino listener: hmi_listener.ino
Later I wrote a Python tool that reports new request instances instead of dumping everything: hmi_registers.txt
And because the HMI makes it difficult to distinguish a page change from a button press just by looking at the request stream, I ended up using some hacky shell markers while testing:
python3 -u hmi_reg.py --live 2>&1 | tee -a out.txt
echo "===========================================" >> out.txt
More developed logger: hmi_register_logger2.py
The HMI only asks for what it needs right now
This was one of the most useful observations in the whole project.
The HMI does not sit there requesting every address referenced anywhere in the entire PLC project. It requests the data needed by the active screen and whatever background functions are currently relevant. Change pages and the request pattern changes.
In my testing, the HMI could remain initialized as long as it continued receiving valid replies to enough of the data it was actively requesting. That means a fake PLC does not necessarily need a giant complete PLC memory image just to get useful HMI communications going.
Returning zero for every value proves communication, but it is not useful. The useful version is to turn the request into a dispatch operation:
switch (requested_register) {
case 100:
return read_temperature();
case 102:
return read_can_pressure();
case 120:
return motor_rpm;
default:
return 0;
}
Now the HMI says, "hey, I want this register." The Arduino decides where that value really comes from, fetches it from the sensor, variable, GPIO, CAN frame, serial device, or whatever else is appropriate, and returns the requested datatype.
You still have to map HMI addresses to Arduino functions. That part is unavoidable. What changed is that I now had tools to discover the map instead of manually hunting through every HMI object.
Undocumented or differently implemented HMI calls
There was another complication. The HMI is clearly using Maintenance Protocol concepts, but some request forms differ from the small set of commands that are well documented in the old protocol material. A compiled HMI project may also optimize related addresses into block reads.
I observed the requests long enough to build this working description:
| Call | Observed use |
|---|---|
RD |
Read D data registers (16-bit words). |
RM |
Read M internal relay bits. |
RA |
Read extended D-register area using hexadecimal addressing. |
R_ |
Timer-related read. |
WA |
Write extended D-register area using hexadecimal addressing. |
WD |
Write D data register(s). |
WM |
Write groups of M bits. |
Wm |
Write one bit. |
Rl |
Observed packed/block bit read. Exact semantics still under investigation. |
I want to be clear about the last category: some of this is experimentally confirmed behavior, and some names are my best description of what the captured requests are doing. That distinction is important when working from packet captures instead of a complete OEM protocol specification.
MiSmHMI
Once I had enough of the HMI request language identified, the obvious next step was to stop treating the HMI experiments as disposable test scripts.
IDEC uses terms including DM Link and DiSM around related HMI communications. I called my API MiSmHMI.
The important difference from MiSmTCP and MiSmSerial is the direction. MiSmTCP and MiSmSerial generally act like clients talking to a real PLC. MiSmHMI presents a small PLC-like register image to the HMI. The HMI is the TCP client, and MiSmHMI answers its requests.
MiSmTCP - client talking to a PLC over Ethernet
MiSmSerial - client talking to a PLC over serial
MiSmHMI - PLC-like server presented to an HMI
At that point the project had stopped being "an Arduino fake PLC" and had started looking more like a collection of interoperable Maintenance Protocol building blocks.
June: realizing how much Maintenance Protocol can actually do
By June I was sitting on two useful Maintenance Protocol transport libraries and started looking more seriously at what the IDEC software suite itself does.
An enormous amount of useful maintenance work eventually turns into some kind of command exchange with the device. Normal register reads are only the beginning. During this period I worked on or identified methods related to:
- bit and word reads/writes
- inputs and outputs
- timers and counters
- multi-register / block access
- 32-bit and floating-point data handling
- PLC operating status and scan information
- Force I/O
- SD-card directory and file operations
- PLC/HMI status and configuration information
- module and expansion-bus discovery
- program upload/download research
- authentication and program-protection research
Some HMI operations also cross into FTP, and firmware/programming workflows can use additional framing. So I no longer claim that literally every byte in the ecosystem is classic Maintenance Protocol. But the maintenance interface is involved in far more functionality than the normal user-facing documentation would suggest.
"IDEC supports Maintenance Protocol" vs "IDEC supports you using Maintenance Protocol"
Here is where things became difficult.
IDEC documents Maintenance Protocol and their products use Maintenance Protocol. But when I asked questions at this level, their practical response was essentially that they were not going to stop me, but this was not an application they provide support for.
Which is a funny thing to hear when modern manuals, product literature, and websites all say the products support Maintenance Protocol.
The distinction is really: the products support it; the support organization does not necessarily support you building your own protocol implementation around it.
That means the additional features became much harder to implement. There is a lot of reference material, but a surprisingly small amount of detailed Maintenance-Protocol-specific implementation material. You can be reading an otherwise unrelated section of an IDEC manual and suddenly find a register mapping or status bit that explains a behavior you have been trying to understand for a week.
Also, IDEC moves documents, old links break, product generations overlap, and information gets scattered between manuals.
Wireshark, fortunately, is easy to find.
Wireshark fills in the blanks
Outside of reading the docs - and I mean a lot of documents - Wireshark has been one of the most useful tools in this project.
The docs may describe a customer-facing method for doing something. Then WindLDR or WindO/I-NV4 will do the same job using a different internal command. Or the documentation will describe a startup requirement but omit the other addresses the running program immediately polls.
Captures let me cross-check both directions:
- What did I send?
- What did the OEM software send?
- What did the PLC/HMI actually answer?
- What changed between the working and failing cases?
One example is expansion-module discovery. There are documented register-based methods for identifying devices, which can require multiple reads as the system grows. The OEM tooling can use a much more compact command that returns a larger view of the bus in one operation.
So they have a one-step method where the customer-facing method takes multiple steps.
BOO.
Pitfalls and wrong leads
A few ideas sounded reasonable enough to spend time on and turned out not to matter:
- IDEC MAC address: I tried making the Arduino look like an IDEC Ethernet device. It did not solve the HMI problem.
- The "magic" HMI time register: useful in some contexts, not the general initialization answer I first thought it was.
- If my custom client accepts the server, the HMI will too: nope.
- Allocate the entire PLC memory map: possible on sufficiently large hardware, but largely unnecessary for the actual HMI/server use case.
This is one of the reasons I like publishing the R&D instead of only the final library. The wrong ideas are useful information too.
Putting it together
PLC Programs take precidence over Maintenance Protocol commands. You can set whatever you want, but if the progam overwrites it, what was the point?
As it turns out, you can implement a PLC's hardware, without a meaningful program. Just like this:
Now, you have Power Controls, via an arduino, without concurency problems.
The first easy test was to make an Arduino provide a random value at a known register, D158, then point my existing custom monitoring tools at the fake PLC. If my own tools could decode and plot it, I at least knew my framing and responses were sane.
Video: Arduino / Maintenance Protocol test
Result image: Success1.png
[IMAGE: fake Arduino PLC data being plotted]
From there the interesting configurations multiply quickly.
Arduino ----\
PLC ---------> Ethernet switch
HMI --------/
PC --------/
Other -----/
Depending on the experiment:
- Arduino -> HMI
- PC -> HMI
- Arduino -> PLC -> HMI
- HMI -> PC/Arduino bridge -> PLC
The PLC and HMI projects are normally configured for specific external-device IP addresses. That means the emulator or bridge needs to appear where the project expects it. I wrote HMI tools to help with that.
Configuration diagram: Configurations.png
[IMAGE: traditional PLC/HMI network vs Arduino-assisted configurations]
Other protocols I ran into
Maintenance Protocol turned out not to mean "literally every communication function uses one identical frame." The ecosystem uses several mechanisms.
| Protocol / transport | What I encountered |
|---|---|
| Maintenance Protocol over TCP | PLC register/control communication, typically using port 2101 in the systems I tested. |
| HMI maintenance on TCP 2537 | HMI maintenance/service operations used by IDEC tooling. |
| UDP | Observed in HMI discovery/selection and application-specific communications. |
| FTP | Used heavily in HMI file/program transfer workflows, with maintenance commands coordinating parts of the process. |
| Other framing | Firmware and programming operations can introduce additional STX/ETX-style or binary transfer mechanisms. |
The HMI work in particular became its own project. I learned enough of the maintenance-controlled FTP workflow to read information, inspect files, and perform useful maintenance operations. Some firmware and protected program-transfer operations remain R&D and are intentionally not presented here as completed public APIs.
Authentication and protected program transfers
Late in the project I started working on the enhanced-security path used around protected PLC program upload/download. That work involved challenge/response behavior, protection status, initialization/erase operations, and the sequencing used before program transfer.
This work is not released as a finished feature. I am mentioning it because it changed the direction of the library. Once program transfer is reliable, Maintenance Protocol stops being only a monitoring/control API and becomes useful for fleet maintenance and factory tooling as well.
That is where the MiSmFactory idea came from: keep ordinary register access in the normal transport libraries, and put higher-risk factory/programming operations in a separate layer with much more deliberate handling.
Documentation became part of the project
At some point the documentation problem itself became a project.
Useful information is scattered across old protocol manuals, FC6A communication manuals, HMI project printouts, special-register sections, application notes, packet captures, and behavior that is visible only when the OEM tools perform an operation.
So I started treating documentation as code:
- keep protocol references in the repository
- write common API documentation for MiSmSerial and MiSmTCP
- record known special registers
- record HMI request forms
- write utilities that discover module configuration and PLC state
- keep captured behavior reproducible instead of relying on memory
This is not glamorous work, but future me appreciates it every single time.
Security and ethics
IDK, the OEM said I could do it, but they also made it clear this is not a topic they provide normal application support for.
Can PLCs be exploited? Of course. They are computers controlling physical processes, and many industrial protocols were designed for trusted networks where "someone on this wire is hostile" was not the primary design assumption.
Does this project create that problem? Not really. The functions being explored are existing device behavior. But making undocumented or poorly documented behavior easier to understand can obviously have security implications. I think the useful response to that is not pretending the behavior does not exist. It is understanding the system well enough to deploy it responsibly.
My practical advice is boring and unchanged:
- Do not put a PLC or HMI directly on the public Internet.
- Use network segmentation.
- Use appropriate firewalls.
- Use secure remote-access infrastructure such as a properly configured VPN or equivalent controlled access.
- Treat legacy industrial protocols as trusted-network protocols unless you have strong evidence otherwise.
There are OEM examples that make Internet-connected features look easy: web servers, e-mail, IoT integrations, remote interfaces, and so on. That does not mean the controller itself should be given unrestricted Internet exposure.
In the news
PLCs are pretty boring honestly, right up until a vulnerable industrial system is not boring anymore. Industrial-control security is not a new problem.
What this project turned into
fc6a.py
|
+--> MiSmSerial
|
+--> MiSmTCP
|
+--> HMI request discovery
|
+--> MiSmHMI
|
+--> PLC/HMI bridges
|
+--> SD-card tools
|
+--> module discovery
|
+--> register maps and protocol documentation
|
+--> upload/download/authentication R&D
|
+--> MCU <-> PLC/HMI integration
The really useful part, at least to me, is the abstraction that fell out of all of this:
Take whatever bus or I/O you actually have, then expose the useful values as PLC bits and registers.
An Arduino does not need to become a complete PLC. It needs to answer the subset of PLC-shaped questions the rest of the system cares about. That is a much more practical target.
Unfinished work
I'm just one guy, and my parent company will be moving away from IDEC over the next year or so. There is more work here than I am likely to finish in one pass.
But at this point I have the pieces needed to build a much more complete Arduino Maintenance Protocol library, and there is no fundamental reason the same approach cannot be carried to serial implementations, ESP32, Teensy, Linux SBCs, and other embedded platforms.
The programming/authentication side also needs more validation before I am comfortable calling it a released feature. Program upload/download is one of the last major pieces needed for the factory-management direction of the project.
Reference material and tools
The best single protocol reference I have found for the old request/reply format is:
MicroSmart Communication Protocol reference
The current project repository: Makerspace-Bangor/fc6a
On The Line repository: Makerspace-Bangor/OnTheLine
Tools I used constantly:
- Wireshark - verify framing and discover what the OEM software is actually doing.
- WindLDR - still the fastest way to program and inspect an IDEC PLC.
- WindO/I-NV4 - HMI project creation and project documentation.
- Python - protocol clients, servers, loggers, bridges, parsers, and test tooling.
- Arduino - cheap hardware for making the protocol do things it was never intended to do.
Thanks
Thank you to Element14 and the On The Line Design Challenge team for providing hardware, documentation, and - more importantly - a reason to focus on this project hard enough that it went in directions I did not expect.
Legal
The FC6A/MiSm project code is open-source, as are the project materials I publish. IDEC PLCs, HMIs, firmware, programming tools, and related intellectual property remain proprietary to their respective owners. This project does not assert ownership over IDEC software, firmware, hardware designs, or licenses. The libraries provide independent tools for communicating with functionality available on systems the user is authorized to operate.
Use this material for education, interoperability research, maintenance, and your own authorized systems. Use it at your own risk, and test control-system changes safely before putting them anywhere near real machinery.
Conclusion
I did not use the provided materials in the way I originally expected, and arguably I did not complete the On The Line Challenge I originally proposed.
Instead, the project wandered into PLC libraries, HMI emulation, Arduino networking, register discovery, SD-card file systems, module discovery, FTP, program-transfer research, authentication, and a lot of protocol analysis.
Nevertheless, I am proud of the work I did while focusing on this project, and I appreciate the opportunity to explore new and very old technologies alike.
I hope you find some of it useful - or at least interesting.