Python to PLC communication looks simple right up until you have to choose *how* Python should query the controller. REST, JSON-RPC or gRPC? Three styles, the same result (reading one variable from the controller), but three completely different calls on the wire.
In this post we break them down on a live example: we read the current shift OEE from a Siemens S7-1200 G2 and a Phoenix PLCnext controller. Along the way we answer the question that comes up in every training session: is the Web API in the S7-1200 G2 the same as a JSON API?
The same read, three ways
Let’s say the controller program has a data block OEE_Data with a variable LastShift.rOEE. In Python we want to get a single number:
"OEE_Data".LastShift.rOEE = 84.72 %We can do this same read in three ways. They differ in philosophy, not in the result:
- REST – “give me a RESOURCE”: the target lives in the URL, the action is the HTTP verb (GET, PUT).
- JSON-RPC – “CALL a function”: one address, and you pass the action in the body (the
methodfield). - gRPC – also “CALL a function”, but binary and over HTTP/2.
Style 1: REST, resource-oriented
In REST every thing is a resource with its own address (e.g. /api/variables), and what you do with it is set by the HTTP verb: GET (read), PUT (write). Data travels as JSON, readable at a glance. This style is used by the Web API in Phoenix PLCnext.
# PLCnext _pxc_api (REST) - "give me a RESOURCE"
r = requests.get(".../api/variables",
params={"paths": "...rOEE"})
print(r.json()["variables"][0]["value"])
# -> 84.72
# on the wire:
GET /api/variables?paths=...rOEE (action = HTTP verb + resource address)Style 2: JSON-RPC, the Siemens S7-1200 G2 Web API
In the S7-1200 G2 Siemens went with JSON-RPC 2.0. There is no long list of addresses here – you always POST to a single endpoint /api/jsonrpc, and you pick the action with the method field (e.g. PlcProgram.Read), put the arguments in params, and the answer comes back in result or error.
First you have to log in with the Api.Login method – the controller returns a token that you add to every following request in the X-Auth-Token header:
# 1) login -> token
login = {"id": 1, "jsonrpc": "2.0", "method": "Api.Login",
"params": {"user": "HMI_access", "password": "********"}}
token = requests.post(URL, json=login, verify=False).json()["result"]["token"]With the token, reading a variable is a single POST:
# Siemens S7-1200 G2 Web API (JSON-RPC 2.0 over HTTPS)
msg = {
"id": 1,
"jsonrpc": "2.0",
"method": "PlcProgram.Read", # action = function name
"params": {"var": '"OEE_Data".LastShift.rOEE',
"mode": "simple"},
}
r = requests.post("https://192.168.0.3/api/jsonrpc",
json=msg,
headers={"X-Auth-Token": token},
verify=False) # PLC has a self-signed cert
print(r.json()["result"]) # -> 84.72
# on the wire:
POST /api/jsonrpc (one address, action in the body)On the controller side you have to enable the web server and the Web API in TIA Portal, and grant the user read and write rights for variables. You will find the full method list in Siemens Industry Online Support.
S7-1200 G2 Web API: is it the same as a JSON API?
Short answer: both yes and no. “JSON API” is a casual name for any interface that accepts and returns JSON over HTTP. In that sense the S7-1200 G2 Web API *is* a JSON API – you talk to it with ordinary HTTPS requests and the payload is JSON.
But technically it is a specific protocol: JSON-RPC 2.0, not a RESTful “JSON API”. The difference is practical and easy to trip over:
- REST (JSON API): different URLs (nouns), the action is the HTTP verb. You read a resource:
GET /variables/.... - Siemens Web API (JSON-RPC): one address
/api/jsonrpc, the action sits in themethodfield in the body. You read by calling a function:PlcProgram.Read. - In JSON-RPC it is always POST – even when you only read. You will not read anything here with a GET.
- Every message has a fixed skeleton:
jsonrpc: 2.0,method,params,id.
Takeaway for practitioners: if you search the web for “Siemens REST API”, you will be disappointed. Search for “S7-1200 Web API JSON-RPC” and treat it as calling functions (login, read, write, logout), not as fetching URLs.
Style 3: gRPC, contract and binary code
gRPC also “calls a function”, but differently than JSON-RPC. First you define a contract in a .proto file (types and services), generate code from it (stubs), and the data travels as Protocol Buffers, a binary format over HTTP/2. Not readable at a glance, but fast and with a ready type contract.
// .proto contract (types + services)
service PlcData {
rpc ReadTag(Req) returns (Reply);
}
message Reply { double value = 1; }
# Python (after generating the stubs)
reply = stub.ReadTag(Req(var="...rOEE"))
print(reply.value) # -> 84.72
# on the wire: HTTP/2 + binary protobufREST vs JSON-RPC vs gRPC: the differences
If you remember one sentence: REST says “give me a RESOURCE”, and RPC (JSON-RPC and gRPC) says “CALL A FUNCTION”. The difference between JSON-RPC and gRPC is in turn text over HTTP versus a binary format over HTTP/2.
- REST – many addresses, HTTP verbs, text JSON. Easy to inspect and debug.
- JSON-RPC – one address, action in the body, text JSON. This is how the Siemens Web API works.
- gRPC – a
.protocontract, binary protobuf, HTTP/2. The fastest, but it requires code generation.
In our project it looks like this: the Siemens S7-1200 G2 uses JSON-RPC, the Phoenix PLCnext uses REST. When to pick what? REST and JSON-RPC for IT, web and cloud integration (they pass through the firewall on port 443). gRPC for fast, binary exchange between services.
What if I don’t want a Web API? snap7 and the native S7 protocol
The Web API is not the only way to a Siemens controller. The python-snap7 library talks to the controller with the native S7 protocol (port 102) – no token login, but with raw bytes that you decode yourself by offsets from the data block:
import snap7
from snap7.util import get_real
plc = snap7.client.Client()
plc.connect("192.168.0.3", 0, 1) # IP, rack, slot
data = plc.db_read(1, 0, 4) # DB1, offset 0, 4 bytes
print(get_real(data, 0)) # -> 84.72This requires PUT/GET communication enabled in the CPU and a data block with standard (not optimized) access – otherwise the byte offsets will not match. snap7 is great for fast block reads, and the Web API for web integrations and getting through a firewall.
Common problems (and how to solve them)
- SSLError / certificate – the S7-1200 G2 has a self-signed cert. In testing use
verify=False, and for production add the certificate to the trusted ones. - HTTP 403 / no permissions – the Web API user does not have “Read tags” / “Write tags” rights. Check the user configuration in TIA Portal.
- Token expired – after a period of inactivity the controller invalidates the token. Log in again with the
Api.Loginmethod. - Variable name – the data block name in quotes, members joined with a dot. A typo ends up as an
errorfield in the response. - snap7: wrong values – the data block is optimized or the byte offset is wrong. Switch the DB to standard access and check the offsets in TIA.
Frequently asked questions (FAQ)
No. It is JSON-RPC 2.0 – a single /api/jsonrpc endpoint with the action in the method field. JSON sent over HTTP is easy to mistake for REST, but it is a different style.
Yes. First Api.Login returns a token that you add in the X-Auth-Token header to every request, and at the end you call Api.Logout.
Web API – when you need web integration, HTTPS and getting through a firewall. snap7 – when you want fast native data block reads on the local network.
gRPC (binary protobuf over HTTP/2). REST and JSON-RPC are slower, but simpler to debug and more universal.
Summary
Python to PLC communication comes down to one choice: resource or function. REST fetches a resource, JSON-RPC and gRPC call a function. The Siemens S7-1200 G2 goes with JSON-RPC (Web API), the Phoenix PLCnext with REST, and gRPC is left for fast, binary exchange between services. Once you know these three styles, you can connect Python to almost any controller.
Want to try it on a live controller and write your own OEE report in Python? Explore our PLC and automation courses at ControlByte Academy.



