Pitot Tube

The Pitot Tube was invented by the French engineer Henri Pitot in 1732 to measure water flow speed. Today it is one of the most common speed-measuring devices, used in aviation, Formula 1, drones and weather stations.
A pitot tube measures total pressure (the ram pressure of the oncoming air = dynamic + static). Its mouth must face the airflow; subtracting the static pressure (from a static port) and applying Bernoulli's equation yields airspeed: v = √(2·(total − static)/ρ).
The Pitot Tube (ccpe:pitot_tube) is an attachable, directional speed-sensor block. When installed on a physics body (Sable sub-level), CC:Tweaked computers on that body can read the velocity component along the tube's mouth axis — both ground speed and airspeed — via ccpe.sensor_system.
The pitot tube measures only the component of motion along its mouth axis, as a signed scalar (positive = moving toward the mouth, i.e. air entering the mouth; negative = moving away). A tube aimed 90° to the motion reads ≈ 0 — aim the mouth where you want to measure.
Signed readings are a mod convenience
A real pitot-static system can only measure the magnitude of airspeed — it cannot tell whether the airflow comes from the front or from behind. The sign in these readings is deliberately added by the mod so scripts can distinguish direction (positive = toward the mouth); it has no real-world counterpart.
Orientation (24 states)
The tube's orientation is described by two block-state properties:
| Property | Values | Meaning |
|---|---|---|
facing |
6 directions | The direction the model's top face points; set to the clicked face on placement |
roll |
0–3 | Rotation around the top-face normal (0°/90°/180°/270°) |
- Placement: right-click a surface —
facing= the clicked face,roll= 0. The tube attaches to the block behind it (opposite thefacingdirection) and drops if that block is removed. - Wrench: right-clicking the model's top face (the face in the
facingdirection) rotates the tube around that face (roll+1,facingunchanged); right-clicking any other face does nothing. - The 24 combinations cover every orientation of the mouth axis — use the wrench (and the selection box) to aim the mouth along the direction you want to measure.
Reading reference point
speed/air_speed are read at the pitot tube's own position:
- Velocity = the world point velocity at the pitot tube (includes the rotational contribution ω×r; same algorithm as
simulated:velocity_sensor), projected onto the mouth axis (the tube's 24-state orientation, rotated to world by the physics body's pose). - A body can carry multiple pitot tubes, each with its own independent reading.
- Readings refresh once per tick (at most 1 tick stale); Lua reads perform zero main-thread scheduling, so high-frequency calls are effectively free.
Pitot-static gate
Speed readings require a complete pitot-static system: the physics body (including constraint chains) must have at least 1 pitot tube AND at least 1 static port at the same time. Without either one, getSpeed()/getAirSpeed() return nil, and the pitot entries in getSensors() carry speed = nil, air_speed = nil.
Physically, airspeed is derived from the difference between total pressure (pitot tube) and static pressure (static port) — a pitot tube alone cannot produce an airspeed reading.
Lua API
A computer on the same physics body (including constraint chains) uses require("ccpe.sensor_system"):
| Method | Returns | Description |
|---|---|---|
getSpeed() |
number / nil | Ground speed along the mouth axis of the most recently placed pitot tube (m/s, signed; positive = toward the mouth). nil when the pitot-static gate fails |
getAirSpeed() |
number / nil | Airspeed along the mouth axis (m/s, signed; relative to the air, wind subtracted). Same gate; differs from getSpeed() only when a wind source is present |
getAverageSpeed() |
number / nil | Simple average of the ground speed of all pitot tubes (m/s, signed component along each tube's mouth axis). Same gate |
getAverageAirSpeed() |
number / nil | Simple average of the airspeed of all pitot tubes (m/s, along each mouth axis, relative to the air). Same gate |
getSensors() |
table | Snapshot of all sensors (same tick); pitot entries carry {type="pitot_tube", pos={x,y,z}, pos_rel={x,y,z}, speed, air_speed} (nil readings when the gate fails) |
Shared methods (isOnBody(), getBodyId(), ...) behave as documented on the Static Port page.
- Ground vs air speed:
getSpeed()uses the corrected world-frame point velocity at the pitot mouth (body-origin translational velocity from Sable's per-tick pose differencelatestLinearVelocity, plus the lever-armω×rterm usinglatestAngularVelocity— never the raw physics-handle values, which report phantom non-zero readings on a stationary body).getAirSpeed()additionally subtracts the wind, derived asSable.HELPER.getVelocity−getVelocityRelativeToAir(both share the same contaminated base, so the phantom values cancel). Sable registers no wind by itself — without a wind-providing mod (e.g. PMWeather) both return the same value. - No dead zone: readings are returned as-is (the velocity source is the clean per-tick world pose difference, exactly 0 when stationary — no phantom values to mask, and slow real motion is no longer hidden).
- Multiple pitot tubes:
getSpeed()/getAirSpeed()use the most recently placed one (registration order = placement order — see the restart warning below);getAverageSpeed()/getAverageAirSpeed()average all tubes (same-tick snapshot) and are immune to ordering.
local ss = require("ccpe.sensor_system")
print("onBody:", ss.isOnBody())
print("ground speed along mouth:", ss.getSpeed())
print("airspeed along mouth: ", ss.getAirSpeed())
print("avg ground speed (all): ", ss.getAverageSpeed())
print("avg airspeed (all): ", ss.getAverageAirSpeed())
local sensors = ss.getSensors()
for i, s in ipairs(sensors) do
if s.type == "pitot_tube" then
print(i, "pos:", s.pos.x, s.pos.y, s.pos.z,
"speed:", s.speed, "air_speed:", s.air_speed)
end
end
Multiple pitot tubes
Each pitot tube has its own independent speed/air_speed reading (same-tick snapshot). getAverageSpeed()/getAverageAirSpeed() give the simple average over all tubes directly (nil when the pitot-static gate fails). getSensors() is the way to read a specific tube:
local sensors = ss.getSensors()
for _, s in ipairs(sensors) do
if s.type == "pitot_tube" and math.abs(s.pos_rel.y - 2) < 0.5 then
print("that tube's airspeed:", s.air_speed)
end
end
After a server restart, getSpeed()/getAirSpeed() may point at a different pitot tube
If the body has only one pitot tube, this warning does not apply.
getSpeed()/getAirSpeed() return data from the most recently placed pitot tube, determined by registration order (= placement order), which is only valid within the current session. After a server restart, pitot tubes re-register in chunk-load order, so these two methods may point at a different tube, and the target may differ between restarts.
If your script must reliably read a specific tube, use getSensors() and identify it by pos_rel (position relative to the current computer) — pos (relative to the body origin) drifts when blocks are added to or removed from the body, since the origin (center of mass) moves.