Category Archives: Automation

Outdoor G-Scale Garden Railroad: Detecting the Trains

A little while ago I told you about an ongoing project to automate my parents’ G-Scale outdoor model railroad. Today I’m going to add a bit more detail about the solution: specifically, how do you sense the location of the trains?

Layout

The layout is broken into 3 lines: a “Figure 8” line, a “Point-to-Point” line, and a “Main Line”, which has various track switches, sidings, etc. The interesting thing about the Figure 8 and Point-to-Point lines is that they cross, and one of the goals is to prevent trains from colliding.

Some other goals include:

  • The point-to-point should start at one station, move to the other station, stopping out of site for a while. It should stop at each end-station for an adjustable period of time, and return.
  • The Point-to-Point and Figure 8 lines have uphill and downhill sections, so the speed needs to be varied in these sections.
  • The Figure 8 line has two programmable stops, but the train shouldn’t necessarily stop at both stops every time.
  • We want to run multiple trains on the main line. Some will be stopped on a siding while others are running, and then they will switch.
  • All tracks need the ability to be manually operated.

The Figure 8 Line from behind "Stan's Speed Shop"

Power

All existing locomotives in the system use “track power” (DC voltage applied across the two rails of the track). The voltage applied to the track is applied to the motors in the locomotives, and this controls the speed.

There are some advantages to this: it allows you to run “stock” (unmodified) engines, and it’s compatible if someone wants to bring over a “guest” engine (either a track powered or battery powered model). It’s also compatible with “DCC” controlled locomotives which, as I understand it, are backwards compatible with track powered systems.

Control?

Whether you use a PLC or a PC for control, being able to control the voltage to a track (to control a motor speed) is pretty much a solved problem, so let’s assume we can do that for now. The main problem is location sensing. In order to tell the locomotives to stop, wait, start, etc., you need to know where they are.

Your first thought, as a controls engineer, is some kind of proximity sensor. Unfortunately there are some significant problems with this:

  • Metal-sensing proxes are expensive, and the locomotives are mostly plastic. We’re trying to avoid retrofitting all locomotives here. You might be able to sense the wheels.
  • Photo-sensitive (infrared) detectors, either retro-reflective or thru-beam type, are popular on indoor layouts, but they apparently don’t work well outdoors because sunlight floods them with infrared.
  • Reed switches are popular, but you need to fit all your engines with magnets, and they are a bit flaky. Magnets and reed switches actually work well if you have the magnet on the track, and the reed switch mounted to the engine, in order to trigger whistles, etc., but even then they’re not 100% reliable, in our experience.
  • All proximity detection strategies require you to run two wires to every sensor, which is a lot of extra wiring. Remember, there are lots of little critters running around these layouts, and they tend to gnaw on wires. Fewer wires is better.
  • Having sensors out in the layout itself means you’re exposing electrical equipment to an outdoor environment. At least you can take the locomotives in at night, but the sensors have to live out there year-long. I’m a bit concerned by that thought.

Solution: Block Occupancy Detection

The solution we found was a technique called “block occupancy detection.” This is a fairly common method of detection in some layouts. A couple of years ago, I built a simple controller that solved the crossing detection problem between the Figure 8 and Point-to-Point lines using block detection to know where the trains were. It worked great, so we decided to use it for the entire system.

Here’s how it works: you divide up your track layout into “blocks”. Blocks can be any size, but they are typically anywhere from about 4 feet long, up to the length of a train, or a bit more. One rail on the line is “common” and isn’t broken up. The other rail is the one you cut into electrically isolated sections.

So, the wire from the common side of your speed controller goes directly to the common rail, as it did before. However, you have to split the “hot” side of your speed controller into as many circuits as you have blocks. Each block is fed from a separate circuit, which means you have to run a “home run” wire from each block back to your power supply.

Then, the “block occupancy detection” circuit is wired in series with each block circuit (between the speed controller and the block). Here’s what one block detection circuit looks like:

This is an interesting circuit. On the left you can see a bridge rectifier, with the + and – terminals curiously shorted out. This is a hacked use of the device. All we really care about is that we want to create a voltage drop across the device when current is flowing through the wire to that block. One diode creates a voltage drop of 0.6 to 0.7 V, and the way we’ve wired it, whether the speed controller is in forward or reverse, the current always has to take a path through two forward-biased diodes. That means, when current is flowing to the block (i.e. there’s an engine in that block) then we get a voltage drop of 1.2 to 1.4 V across this device (or -1.2 to -1.4 V if it’s in reverse). A standard bridge rectifier is just a handy component to use because it’s readily available in high current ratings for a couple of dollars each.

We’re using that constant voltage to drive the input side of an LTV-824 opto-isolator chip. Notice that it’s a bi-directional opto-isolator, so it works in forward or reverse too. On the output side of the opto-isolator, we can run that directly into a PLC input (the input we’re working with here is sourcing and has a pull-up resistor built-in).

If you’re using a regular straight-DC analog controller, that’s all you need, but in this case we’re using a pulse-width-modulated (PWM) speed controller. That means the PLC input is actually pulsing on and off many times a second, and if you’re at a slow speed (low duty cycle), then the PLC may not pick up the signal during it’s I/O scan. For that reason, I found that sticking a 1 uF capacitor across the output will hold the PLC input voltage low long enough for it to be detected quite reliably. This, of course, depends on your pull-up resistor, so a little bigger capacitor might work better too.

Filtering in the PLC

This worked quite well, but needed a bit of filtering of the signal in the PLC. The input isn’t always on 100% while the locomotive is in the block, so once a block is latched as “occupied”, I use a 1-second timer of not seeing the input on before I decide that the block is clear.

I also have to see an adjacent block occupied before I clear a block. That solves the problem of “remembering” where an engine is when it stops on the track and there’s no longer any current flowing to that block.

Of course, this means you can end up with “ghosts” (occupied blocks that are no longer truly occupied because someone picked up a locomotive and physically moved it). I provided some “ghost-buster” screens where you can go in and manually clear occupied blocks in that case.

Pros and Cons

I like this solution for several reasons: all the electronics are at the control panel, not out in the field (except the wires to each block, and the track itself). Also, the components for one block detector are relatively inexpensive, and we’re working on a bit of a budget here (it is a hobby, after all). I think reliability and simplicity also fall into the Pro column. As long as you can get a locomotive to move on a segment of track (the track needs cleaning from time to time), then the PLC should be able to detect it. You don’t need to deal with dirty photo-detectors or extra sensor wires. The same wire that carries the current to the block carries the signal that the block is occupied.

On the other hand, there are some negatives. This system, as designed, has 21 different blocks, which means 21 home-run wires, buried in the ground, in addition to the commons (plus the track switch wires, but that’s a story for another post). Also on the negative side, you don’t get 100% accurate position sensing. Actually, you get pretty accurate sensing at the edges of the blocks (you’re pretty sure you know where the locomotive is the moment is crosses from one block to the next), but you’re not sure where it is in the middle of the block.

You do have to make other compromises in the track system. There are some accessories (like lighted end-stops) that draw power from the track. This current draw makes that particular block show up as occupied all the time. You either have to modify the accessory to use battery power, or you have to run extra wires to that accessory.

You also have to take the length of train into account. You know which blocks are occupied by current-drawing locomotives and cars (like lighted observation cars and caboose’s), but not every car draws power. Your design and control system needs to take into account whether or not your train will occupy more than one block at once, and whether the end of the train will be detected. This is most important when trying to run multiple trains on one track, where you want the back train to avoid running into the end of the first train.

Next

I hope that’s been educational. 🙂 I’m still not done programming the PLC, and I’m waiting for a component to arrive for the throttle controller right now. I’ll post more information over the next few weeks.

Sometimes it’s Better to Repeat Yourself

In programming, we have a principle called Don’t Repeat Yourself (DRY). It’s a very important idea, and I’d argue that most of the advances in programming environments over the years have been in support of this principle and its related principle, Once and Only Once (OAOO).

Unfortunately, like every “principle”, it eventually takes on the level of dogma, and the people spouting it sometimes forget why it exists. These principles aren’t ends in themselves; they’re not self-justified. They are general principles to follow, but only when they support the end-goal of solving problems in more efficient, and more maintainable ways.

Let me give you a very simplified example of how it can be carried to far. Consider the following declarations in C#:

const int MOTOR_1_START_TIMEOUT_MS = 5000;
const int MOTOR_2_START_TIMEOUT_MS = 5000;

Consider that I could write:

const int MOTOR_1_START_TIMEOUT_MS = 5000;
const int MOTOR_2_START_TIMEOUT_MS = MOTOR_1_START_TIMEOUT_MS;

or…

const int MASTER_MOTOR_TIMEOUT_MS = 5000;
const int MOTOR_1_START_TIMEOUT_MS = MASTER_MOTOR_TIMEOUT_MS;
const int MOTOR_2_START_TIMEOUT_MS = MASTER_MOTOR_TIMEOUT_MS;

Notice that all 3 versions accomplish the same end-result, but they are semantically different. The first version means that the two motors have independent timeout values, and they’re just co-incidentally the same. The second says, “motor 2’s timeout must be the same as motor 1’s timeout.” The third says that both motors must have the same timeout.

In my opinion, any of these three versions might be correct for various systems involving two motors. However, if you follow the DRY principle without thinking about it, you’ll assert that the first version is incorrect. In fact they’d probably say the only correct version should be:

const int MOTOR_TIMEOUT_MS = 5000;

(…ignoring, for the moment, that it should probably be a configurable value rather than a constant.)

Why does this simple example matter? Consider the case of a PLC-based control system with 10 motors. Let’s say at the start that all the motors, and all the drives running them, are identical. If you’re familiar with my philosophy of PLC programming, you know that my default solution for this would be to have 10 ladder logic routines, each called MOTOR_01, MOTOR_02, etc. Each routine would basically be a copy. That really doesn’t follow the DRY principle, does it? Certainly no, not at face value.

You might not believe it, but I get the occasional “hate mail” to my blog’s email address because of some of my technical opinions here. The most recent one, comically, referred to me (and all PLC programmers for that matter) as “dinosaurs”. I’m not sure what the rest of the message said, because if you can’t be polite, I’m not going to bother listening to you. However, I believe it’s this flagrant violation of things like the DRY principle that really rubs traditional PC programmers the wrong way when you start to talk about the principles of PLC programming.

Of course, my views about PLC programming are just that – general principles that need to be evaluated in the light of each and every project. I’m just asserting that most of the time you should be following a principle of a one-to-one mapping between ladder logic and real-world hardware. That doesn’t mean it’s an unbreakable rule.

Going back to the 10 motor example, the way you structure your program should be based on a decision you make about anticipated future changes to the system.

If you write one generic routine for controlling a motor, and you call it 10 times, you’re saying, “I always expect all 10 of these motors to behave in an identical way for all of the future.” Of course, you can allow variations, but you have to do that by passing in parameters for each instance. You have to be explicit about what can vary. Adding new parameters is typically a harder task than just modifying one of the 10 existing motor routines when you need to change the behavior of one motor.

On the other hand, if you follow my principle of 10 motor routines for 10 motors, you’re saying, “I expect that we’ll rarely need to make a sweeping change to all 10 motor control routines, but that we are likely to modify one or two routines to make them perform differently than the others.” I personally believe this is usually closer to the truth. As a system ages, perhaps one motor drive will blow, and you can’t buy the original drive anymore, so you have to replace it with a new one that has different control signals. That’s a fairly typical scenario, in my experience. Also, even though you might have 10 identical drives and motors, the process may or may not be identical for each motor. They may perform vastly different functions, and it’s likely that you’ll want to change just one or two of them to access more advanced features of the drive when you refine the process. Of course, I also like that with a one-to-one mapping in a PLC, troubleshooting becomes much easier because with online monitoring you can see each control routine executing just for that motor. You can make temporary changes just to one motor routine to bypass a faulted drive, or to do a million other changes that you’ll never be able to predict when you’re writing the logic.

The fact is, we’re physically limited by the number of drives we have. The amount of time it takes to make a change to all 10 motor control routines is tiny compared to how long it takes to make physical changes to 10 drives. This effort scales with the size of the system. In PC programming, you can have a system with millions, even billions, of objects, but in the PLC world, you’re limited by physical reality. The consequences of repeating yourself aren’t always as great, and you need to take that into account, and weigh it against your other goals.

That doesn’t mean I can’t imagine a case where you really want to assert that the motors all have to operate identically, all of the time, forever in the future. There are systems with load sharing drives where the system wouldn’t operate if you mismatched the drives or motors. That’s a design decision you have to make. Principles are only there for guidance, but they are not absolute rules, and they shouldn’t be treated that way.

Sneak Peak: Outdoor Model Railroad Automation

Several years ago my parents decided they were going to build a Garden Railroad in their back yard. It’s been an ongoing hobby project since then, and it’s been growing substantially every year:

West Station on the Point to Point Line

This is “G Scale” (around 1:22.5 scale) outdoor model railroading. It’s really a combination of three things: model railroading, building miniatures, and gardening. This recent weekend was the local club’s open-house day, and I was invited along to see many of the layouts. Each layout kind of emphasizes its own focus: some are more focused on gardening, others on the miniatures, etc.

Anyone who does anything remotely related to computers knows that every relative you have thinks you know everything there is to know about computers, and you’re destined to spend the rest of your family holidays removing spyware and running ccleaner on their computers, not to mention reassuring them that it’s OK to reboot the computer to see if the problem goes away.

Being in industrial automation, though, you never get people asking you to automate something; it’s just a little bit too abstract for most people to grasp. However, when your parents get themselves a model train set, they may not know exactly what you do for a living, but they certainly know that if you can program conveyors, robots, and cranes, you should be able to figure out how to make their trains do what they want them to do. Automatically.

Of course, as a control systems guy, you can’t look at your parents’ 24V model train set and not think about how you’d hook up a PLC to it. Especially when they offer to finance the project.

I’m happy to report that we’re progressing well. The goal is to have it running in fully automatic mode before the end of the month. I’ll post some pictures, hopefully some videos, and some technical information about how it was accomplished. Stay tuned.

Designing Database Tables for Automation People

It may seem like I’ve forgotten about this blog lately, but that’s not the case. The truth is last week I was on vacation, and before and after that I’ve been working on a project tangentially related to home automation, which I’ll probably be posting lots about in a couple of weeks.

However, today I wanted to touch on a topic that many of you will be familiar with: database design. When we talk about database design, we mean a database schema or, more generally, and entity relationship diagram (ERD).

If you do any kind of data logging, or you’re using a database as the data-store for your configuration data, you’ll have to do some kind of database design. Both of these cases call for a “normalized” design. In fact, de-normalized designs are typically only used for heavy-duty data-mining applications, so they’re pretty rare. The advantage of a normalized database is that it follows the “once and only once” (OAOO) software development principle, that says there should be one, and only one, definitive source for any particular fact. So, for instance, don’t store the operator’s name all over the place; rather, store the operator’s name in a table called Operator, include an OperatorId column that’s assigned once when the operator’s row is created but never changes, and then use the OperatorId as a foreign key in your other tables. This gives you several advantages: less database storage (an Id is typically shorter than a name), a single place to change the name (typos are always common and people change their names) and if you do have to change it, you only have to lock one database row to do the edit during the database transaction, instead of every database row that uses this person’s name.

That’s pretty standard stuff, but I want to take a slight tangent. By default, don’t store data you can calculate from other data. This is actually for the same reason. For instance, you wouldn’t store a person’s age, you’d store their birth date. That’s because the age changes all the time. I’m not saying you’d never store a calculated value, but doing so is an optimization, and “premature optimization is the root of all evil.”

Let me give you a real-life example. Lets say you wanted to record the production throughput of an automobile assembly line. Let’s assume you’re already storing the VIN numbers of each vehicle, along with some other data (various part serial numbers, etc.). I’ve seen implementations where someone’s added a new table called LineThroughput, with one row per time period, and a counter in each row (in fairness, I’ve done it too). Every time a vehicle comes off the line, the application finds the applicable row and increments the counter (or adds a new one as required). PLC programmers are particularly likely to do this because we’re used to having limited memory in the PLC, and PLCs come with built-in counter instructions that make this really easy. However, this is a subtle form of denormalization. The database already knows how many vehicles were made, because it has a record for each VIN. All you have to do is make sure that it has a datetime column for when the vehicle rolled off the line. A simple query will give you the total number of vehicles in any time period. If you follow the route of adding the LineThroughput table, you risk having a numerical discrepancy (maybe the database isn’t available when you go to increment the counter, for instance).

Just storing the datetime field has one more advantage: the database is more “immutable”. If data is only written, errors are less likely. If you do want to create a summary table later (for performance reasons because you query it a lot), then you can create it when the time period is over, and once you’ve written the record, you’ll never have to update the row. Again, this is better because the row is “immutable”. The data is supposed to be a historical record. Pretend it’s written in pen, not pencil. (You might be horrified to know that some electronic voting machines seem to use the LineThroughput table method to record your votes, which makes them extremely susceptible to vote-tampering.)

I hope that’s enough information to make my points: normalize your database, don’t record redundant information, or information you can calculate, and avoid situations where you have to update rows repeatedly, particularly if you’re doing data logging.

RAB Telecom Canada Review

I was recently in the market for some laser-printable wire labels and I stumbled across RAB Telecom Canada. The price was right for the smaller quantity I was after, so I decided to give them a shot. I was a bit confused by some of the wording on the website, so I contacted them.

The next day I had not only an email answering my question, but a personal phone call from the president, Richard, apologizing for the confusion, promising to have the site updated promptly, and he personally had the shipment in hand and ready to go. He added, “I will pay the shipping handling and for the labels in question. Mr. Whitlock I do this because I value your possible future business.”

As promised, I recently received the labels in great condition. It’s honestly some of the best customer service I’ve ever received from any vendor. It was so out-of-the-ordinary and unexpected that it shocked me. If you happen to be reading this because you typed RAB Telecom Canada into Google and arrived here, then let me assure you they exceeded my expectations.

The Role of the Engineer

John’s comment on a previous blog post got me thinking about the role of engineers in our society.

Primarily we optimize. Don’t get me wrong, there are lots of engineers that are innovators, inventors, and entrepreneurs, but I believe we’re stepping out of the core role of an engineer when we do that. Our core competency is to get more output from less input.

We’re flexible in our optimization. If we need to get a product to market fast, we can reduce the development time at the expense of resources or quality. Likewise, we relentlessly try to drive down the cost, as profit is the motivating factor behind the businesses that employ us. We do all of this within physical and legal constraints, like the laws of physics and building codes.

That’s why I think it’s funny when anyone proposes that we can “engineer” our way out of some environmental crisis. Engineers will only solve environmental problems if the environmental parameters are included in the equation. If you raise the price of oil, we’ll redesign our processes to use less oil. If you put environmental regulations in place, we’ll redesign our products to meet that criteria.

When I say that something like climate change is a problem for lawyers and politicians, not engineers, this is what I mean. It’s not a technological challenge. It’s a societal challenge. If you really want to consume less fossil fuels then replace income taxes with a fossil fuel tax and see what happens: we’ll automatically shift to other sources of energy, for we are the instruments of that policy.

As engineers, we do have some say. In our role as citizens we vote. We are involved in the creation of new building codes and international standards. Even so, it’s political will that makes change. If you’re waiting for engineering to solve these big issues, don’t. Engineering will follow policy.

Functional Programming in Ladder Logic

There’s a lot of stuff that falls under the term “functional programming,” but I’m just going to focus on the “functional” part right now, meaning when you define the value of something as a function of something else.

In ladder logic, we define the values of internal state (internal coils or registers) and outputs. We define these as functions of the inputs and internal state. We call each function a “rung”, and one rung might look like this:

Ladder diagram of Inputs A and B, and Internal State C

There’s something slightly odd going on in that rung though. You might say that we’ve defined C recursively, because C is a function of A, B, and itself. We all know, of course, that the PLC has no problem executing this code, and it executes as you would expect. That’s because the C on the right is not the same as the C on the left. The C on the right is the next state of C and the C on the left is the previous state of C.

Each time we scan, we redefine the value of C. That means C is an infinite time-series of true/false values. Huh?

Ok, imagine an array of true/false (boolean) values called “C”. The lower bound on the array index is zero, but the upper bound is infinite. C[0] is false (the value when we start the program). Then we start scan number 1, and we get to the rung above, and the PLC is really solving for is this:

Ladder logic defining C[1] as a function of A, B, and C[0]

If that were actually true (if it had an infinite array to store each coil’s value), then the ladder logic would be a truly functional programming language. But it’s not. Consider this:

Two ladder logic rungs with inputs A and B, internal coil C, and output D

In all modern PLCs, the first rung overwrites the value of C, so the second rung effectively uses the newly computed value for C when evaluating D. That means D[1] is defined as being equal to C[1] (the current state value of C). Why is this weird? Consider this:

Two previous rungs with the rung order reversed

By reversing the order of the rungs, I’ve changed the definition of D. After the re-ordering, D is now defined as C[0] (the previous state value of C) rather than C[1]. This isn’t a trivial difference. In an older PLC your scan time can be in the hundreds of milliseconds, so the D output can react noticeably slower in this case.

In a truly functional language, the re-ordering either wouldn’t be allowed (you can’t define D, which depends on C, before you define C) or the compiler would be able to determine the dependencies and re-order the evaluation so that C is evaluated before D. It would likely complain if it found a circular dependency between C and D, even though a PLC wouldn’t care about circular dependencies.

There are a few of reasons why PLCs are implemented like this. First, it saves memory. We would have to double our memory requirements if we always wanted to keep the last state and the next state around at the same time. Secondly, it’s easier to understand and troubleshoot. Not only does the PLC avoid keeping around two copies of each coil, but the programmer only has to worry about one value of each coil at any given point in the program. Third, the PLC runtime implementation is much simpler. It can be (and is) compiled to a kind of assembly language that can run efficiently on single threaded CPUs, which were the only CPUs available until recently.

Of course this comes with a trade-off. Imagine, for a moment, if rung-ordering didn’t matter. If you could solve the rungs in any order, that means you could also solve the rungs in parallel. That means if you upgraded to a dual-core CPU, you could instantly cut your scan time in half. Alas, the nature of ladder logic makes it very difficult to execute rungs in parallel.

On the other hand, we can still enforce a functional programming paradigm in our ladder logic programs if we follow these rules:

  • Never define a coil more than once in your program.
  • Don’t use a contact until after the rung where the associated coil has been defined.

That means there should only be one destructive write to any single memory location in your program. (It’s acceptable to use Set/Reset or a group of Move instructions that write to the same memory location as long as they’re on the same or adjacent rungs).

It also means that if coil C is defined on rung 5, then rungs 1 through 4 shouldn’t contain any contacts of coil C. This is the harder rule to follow. If you find you want to reference a coil before it’s defined, ask yourself if your logic couldn’t be re-organized to make it flow better.

Remember, someone trying to solve a problem in a PLC program starts at an output and uses cross references to move back through the program trying to understand it. Cross referencing from a contact to a coil that moves you forward in the program doesn’t require any logical leaps, but cross referencing to a coil later in the program means you need to logically think one scan backwards in time.

Benefits

While ladder logic isn’t a truly functional language, you can write ladder logic programs in the functional programming paradigm. If you do, you’ll find that your outputs react faster, and your programs are easier to understand and troubleshoot.

How to Read Industrial Control System Wiring Diagrams

I write a lot about the PLC side of industrial automation, but it’s also fundamental to have a good foundation in the electrical side of things.

First of all, most modern (North American) industrial control system wiring diagrams have a relatively common numbering scheme, and once you understand the scheme, it makes it fairly easy to navigate the wiring diagram (commonly called a “print set”).

Let’s start with the page and line numbering. Most multi-page wiring diagrams use a two digit page number (page 1 is “01”). In the rare, but possible, event that you end up with over 99 pages, some diagrams will just add 3 digit page numbers (starting at “100”), but if there was any forethought, many designers will divide their wiring diagrams into sections, giving each section a letter (let’s say “A” for the header material, “B” for power distribution, “C” for safety circuits, etc.). Within each section, you can re-start the page numbering at “01”. This has the added bonus of letting you insert more pages into one section without messing up the page numbering.

Within a page, you’ll typically see line numbers down the left side (and frequently continuing down the middle if you don’t need the whole width of the page for your circuits). These numbers will start with the two digit page number, followed by a two digit line number. Typically these start at zero, and increment by twos:

  • 1000
  • 1002
  • 1004
  • 1006
  • … and so on

Now, devices (like pushbuttons, power supplies, etc.) usually have a device ID based on the four digit line number where they are shown in the wiring diagram, possibly with a prefix or suffix noting the device type. So, if you have a pushbutton on line 2040, the device ID might be PB2040 or 2040PB. The device ID should also be attached to the device itself, normally with an indelible etched label (lamacoid). Therefore, if you find a device in the field, you should be able to find its location in the wiring diagram. (Finding the wiring diagram, of course, is often the more difficult task.)

Wires are numbered similarly. The wire number is typically based on the four digit line number where the wire starts, plus one extra digit or letter in case you have more than one wire number starting on the same line. So, the wire numbers for 2 wires starting on line 1004 might be 10041 and 10042.

It’s typical for wires to connect to devices that are on other pages (it’s extremely common, in fact). In that case, you’ll see off-page connectors. The shape of these vary based on whose standard was used for the wiring diagram, but they’re typically rectangles or hexagons. In either case, inside the shape will be the four digit line reference number where the wire continues. The other end of the off-page connector (on the other page) also has an off-page connector in the opposite direction. Note that you frequently see connectors from one place on a page to another place on the same page, if it happens to improve readability.

That’s all you really need to know to find devices and follow wires in a wiring diagram. Now, to understand the components in an industrial control system, that’s going to take longer than a blog post. For a great introduction, I recommend the book Industrial Motor Control by Stephen Herman. Google books has a great preview if you want to check it out.