thompsonplc
@lthompson · Mayıs 2025 tarihinde katıldı
Automation engineer · pharma · 14 years in the field
- Konular
- 7
- Yanıtlar
- 65
- Puan
- 0
Oluşturulan forum yanıtları
Aoife put her finger on it. We run AGVs in a pharma warehouse and had the same failure until we changed three things.
Mechanical: let the dock do the final alignment. A V-shaped funnel or two angled guide rails that steer the robot into position in the last 15 cm, with spring-loaded contacts that have some travel. IR only has to get it into the funnel. Michael’s wider contacts help, but the funnel is what fixes the crooked case.
Logic: “docked” means charging current flows, not “IR is strongest”. After stopping, wait 2 s and read the charge current. If it’s not there, back off 30 cm, approach again, up to 3 tries. After 3 failures stop trying and report it.
Twin side: place each dock (if it reports anything) on the room asset it sits in, and place the robots on the site, since they move between zones and report their zone in telemetry. Then derive a status key for the robots, either on the Pi or in the root flow: critical after 3 failed docking tries or battery under 15 %, warning while it’s retrying, ok otherwise. Site status takes the worst status of everything placed under it, so a stuck robot turns the hall red on the site view without anyone opening telemetry. Also store a numeric copy (0/1/2), because text keys have no chart history. How the status rolls up: Site status and the status key.
We move thick, fibrous stuff in pharma waste lines, and the pump question comes up there too.
Real immersion PD pumps are rare. The one type that can sit in the liquid is an air-operated double-diaphragm pump (AODD). It runs on compressed air, has no electrics in the pit, tolerates solids and runs dry without damage. With stainless or ductile iron body and flap valves (not balls) it handles fibrous slurry well. You need a compressor and the flow pulses, so put a pulsation dampener before the separator if it minds.
The other honest options keep the pump dry:
A progressive cavity pump next to the pit, below its lowest level, with a short flooded suction and a macerator in front for the straw. That’s the classic separator feed and gives the steadiest flow. A hose (peristaltic) pump, which can self-prime several metres and doesn’t care about fibres, at the cost of hose changes.
For the screen: the core set only has a Motor pump, but the Process library has Diaphragm pump, Peristaltic pump, Gear pump and Submersible pump, and the Wastewater library has a Screw pump (closest look to a progressive cavity) and a Dosing pump, if your organisation has those libraries. Pump symbols take Running, Speed and Direction values plus the hand/auto Mode badge, so bind Running to something like pump_running and Speed to the VFD’s output in percent. If the device sends keys named after the symbol id (p1.running, p1.speed), they connect by name without picking.
Yapay zekâ tarafından English dilinden çevrildi ·
Sorun seviyede. Pano kontrolü yalnızca profil kataloğunda bulunan, Basic seviyesinde ve yüksek etkili olmayan komutları gönderir. Privileged, Critical ve yüksek etkili olarak işaretlenmiş her komut bilerek panodan reddedilir (“cihaz konsolundan gönderin”), bu yüzden seçici bunları göstermez.Yükleme rampası kapımızda da aynı yöntemi kullanıyoruz. gate.pulse değerini Basic olarak ayarlayın ve yüksek etki kutusunu işaretlemeyin, ardından düğmenin eyleminde “Çalıştırmadan önce sor” seçeneğini açık bırakın; böylece operatör yine bir onay penceresi görür (sunucu da bunu zorunlu kılar, başka bir istemciden kimse atlayamaz). Bir durum satırını door_closed değerine bağlayın ki insanlar ne bastıklarını değil, kapının ne yaptığını görsün. Buna kimin basabileceği izinlerle belirlenir: Widget komutlarını çalıştır ve temel komutları gönderme izni. Gece vardiyası bunları alır, yalnızca panoya bakan kişiler almaz.Ve ESP32 tarafında bir nokta: bir komut iki kez gelebilir. Son 32 istek kimliğini 10 dakika boyunca saklayın ve aynı kimliği tekrar çalıştırmayın. Her darbede açılıp kapanan bir kapı için bu önemlidir; yinelenen bir komut kapının yeniden açılıp kapanması demektir.Pano kuralları sayfası: Control cards.
It’s the tier. A board control only sends commands that are in the profile’s catalogue, Basic tier, and not high impact. Privileged, Critical and anything marked high impact get refused from a board on purpose (“send it from the device console”), so the picker doesn’t offer them.
We run a loading-bay door the same way. Set gate.pulse to Basic and untick high impact, then leave “Ask before running” on in the button’s action, so the operator still gets a confirm dialog (the server enforces it too, nobody skips it from another client). Bind a status line to door_closed so people see what the door is doing, not what they pressed. Who may press it is down to permissions: Run widget commands plus permission to send basic commands. Night shift gets those, people who only look at the board don’t.
And one thing on the ESP32 side: a command can arrive twice. Keep the last 32 request ids for 10 minutes and don’t run the same id again. For a gate that toggles on each pulse this matters, a duplicate means it opens and closes again.
Page with the board rules: Control cards.
Yapay zekâ tarafından English dilinden çevrildi ·
TLS kütüphanelerinin güncel olması için üzerine güncel bir Raspberry Pi OS Lite (legacy) kurun. Hermetry, ECDHE paket şifreleme takımlarıyla TLS 1.2 veya daha yeni bir sürüm istiyor ve 2013 tarihli bir imaj bağlanamaz.
Put a current Raspberry Pi OS Lite (legacy) on it so the TLS libraries are up to date. Hermetry wants TLS 1.2 or newer with ECDHE suites, and a 2013 image won’t connect.
Jessica nailed the main issue: at 15-20 cm you’re inside the blind zone. Mount it higher, a short PVC pipe section as a stand-off works, so full tank is at least 30 cm away.
Second issue is the Python timing. Busy-wait loops on a Pi 3 miss edges whenever the scheduler steps in. Use gpiozero with the pigpio pin factory, it timestamps edges in the daemon:
from gpiozero import DistanceSensor
from gpiozero.pins.pigpio import PiGPIOFactory
import statistics, time
s = DistanceSensor(echo=24, trigger=23, max_distance=4.5,
pin_factory=PiGPIOFactory())
def level_cm():
vals = []
for _ in range(7):
vals.append(s.distance * 100); time.sleep(0.1)
return statistics.median(vals)
Median of 7 throws away the odd echo off the tank wall. Then send {"distance_cm": d, "level_pct": ...} as numbers and put an alarm rule on level_pct below 20 with a debounce of 3 readings, so one bad echo doesn’t wake the warden.
Exactly that, pi3-disable-bt was there with a comment “for chiller” from 2019. Swapped to pi3-miniuart-bt + core_freq=250, hci0 is up, all eight tags show in a scan and the chiller polling didn’t miss a beat. Config change only, so validation treats it as a minor change.
Yapay zekâ tarafından English dilinden çevrildi ·
^ evet. Ayrıca açılış (boot) işlevi olmayan GPIO pinlerini kullanın; bu yüzden döngüler için 0, 2, 12 ve 15 numaralı pinlerden kaçının.
^ yes. And use GPIO pins that have no boot role, so avoid 0, 2, 12 and 15 for the loops.
Power cycled, yes. pi-bluetooth is installed. systemctl status hciuart says failed, “Failed to start Configure Bluetooth Modems connected by UART”.
The String is gone before you use it. (String("barn-") + WiFi.macAddress()) makes a temporary that’s destroyed at the end of that line, so clientId points into freed memory. Whatever the heap does next ends up in your client ID. Bit me on a sensor board years ago and I wasted a day too.
Same trap with setServer(host.c_str(), ...): PubSubClient keeps the pointer, not a copy, so if host is a local String in setup() you’ll get odd reconnect failures later.
Use buffers that live as long as the client:
static char clientId[32];
static char brokerHost[64];
void setupMqtt() {
uint8_t mac[6];
WiFi.macAddress(mac);
snprintf(clientId, sizeof(clientId), "barn-%02x%02x%02x", mac[3], mac[4], mac[5]);
strlcpy(brokerHost, settings.host.c_str(), sizeof(brokerHost));
mqtt.setServer(brokerHost, 8883);
}
// later
mqtt.connect(clientId, settings.token.c_str(), "");
settings.token.c_str() is fine inside the call as long as settings lives globally.
Yapay zekâ tarafından English dilinden çevrildi ·
İyi, o zaman her şey sadece kuru kontak ve bu kolay. Bir süre önce bir müşterinin deposunda hemen hemen aynı şeyi yaptım.Ölçtüğünüz 2.2k, hat sonu (EOL) direncidir. Eski panel, “kapalı”, “kablo kesik” ve “kısa devre” durumlarını ayırt etmek için bunu kullanıyordu. İki seçeneğiniz var:
- En basiti: Bunu atlayın veya yok sayın ve her hattı dijital giriş olarak okuyun. Kapalı kapı = toprağa 2.2k, ESP32 dahili pull-up direnci (yaklaşık 45k) ile LOW olarak okunur. Açık = HIGH. Bu çalışır, ancak kesik bir kablo, açık bir kapıyla tamamen aynı görünür.
- Gözetimi koruyun: Her hattı 3.3 V’a 10k pull-up ile bir ADC pininden okuyun. Kapalı yaklaşık 0.6 V, açık 3.3 V, kısa devre 0 V olur. Böylece ayrı bir
faultdeğeri gönderebilirsiniz.
20 hat için kullanılabilir ADC pinleri tükenecektir, bu yüzden dijital girişleri kullanmanızı veya girişler için bir MCP23017 I2C genleştiricisi eklemenizi öneririm.PIR’ler: Dolaba bir 12 V kaynak koyun, PIR’leri bundan besleyin ve N.C. röle kontağını tıpkı bir kapı gibi bir girişe bağlayın. 12 V negatifini yalnızca gerekirse ESP32 toprağına bağlayın, röle zaten izoledir. Kurcalama kontakları, birinin bir kapağı açtığını bilmek isterseniz onlar da girişlere bağlanabilir.Hermetry tarafında, her girişi true/false yerine 0/1 olarak gönderin ki bunun grafik geçmişini alabilesiniz. Şöyle bir şey:
{"door_lobby": 0, "door_dock": 1, "pir_hall": 0, "tamper_any": 0}
Her değişiklikte ve ayrıca dakikada bir gönderin ki son değer güncel olsun. Ardından profilde door_dock == 1 gibi bir alarm kuralı ve mesai dışı saatler için bir zaman koşulu ekleyin.
Good, then everything is just a dry contact and this is easy. I did almost the same thing for a client’s warehouse a while back.
The 2.2k you measured is the end-of-line (EOL) resistor. The old panel used it to tell “closed” from “wire cut” from “short”. You have two choices:
- Simplest: bypass or ignore it and read each loop as a digital input. Closed door = 2.2k to ground, which with the ESP32 internal pull-up (around 45k) reads as LOW. Open = HIGH. That works, but a cut wire looks exactly like an open door.
- Keep the supervision: read each loop on an ADC pin with a 10k pull-up to 3.3 V. Closed is about 0.6 V, open is 3.3 V, shorted is 0 V. Then you can send a separate
faultvalue.
For 20 loops you will run out of usable ADC pins, so I’d do digital inputs, or add an MCP23017 I2C expander for the inputs.
PIRs: put a 12 V supply in the closet, power the PIRs from it, and wire the N.C. relay contact to an input exactly like a door. Tie the 12 V negative to the ESP32 ground only if you need to, the relay is isolated anyway. The tamper contacts can go to inputs too if you want to know when someone opens a cover.
On the Hermetry side, send each input as 0/1, not true/false, so you get a chart history of it. Something like:
{"door_lobby": 0, "door_dock": 1, "pir_hall": 0, "tamper_any": 0}
Send on every change plus once a minute, so the latest value is fresh. Then an alarm rule like door_dock == 1 on the profile, and a time condition for after hours.
Aisha and Ciaran have the two causes. I did this for a Nano in an air handler once, here’s the whole recipe for the ZS-040 board.
1. Put the HC-05 in AT mode: hold the little button while powering it, the LED blinks slowly (about every 2 s). Talk to it at 38400 from a USB-serial adapter. 2. Set it up (115200 if your Nano works with “ATmega328P”, 57600 if only “Old Bootloader” works):
AT+ROLE=0
AT+UART=115200,0,0
AT+POLAR=1,0
AT+POLAR=1,0 makes PIO11 go low while a link is up. 3. Reset wiring. The ZS-040 doesn’t bring PIO11 out to a header pin, so solder a thin wire to pin 32 of the module. Connect it to the Nano’s RESET through a 0.1 uF capacitor, the same way the USB chip’s DTR does it. When avrdude opens the port, the link comes up, PIO11 drops, the capacitor turns that into a short reset pulse, and the bootloader is listening. 4. Disconnect STATE from RESET.
It’s fiddly, and the first sync after pairing sometimes fails, so try twice before you start debugging again.
That was it. The HAL_Delay(5) was something I’d added while guessing, removing it and waiting on TC gives me all 9 bytes and the CRC checks out every time. No more echo either. Moving to idle line DMA next like Aoife suggested.
Neither, the request decodes fine on the scope and the meter replies to it. It’s my side.
Don’t build the ADC/DAC thing. If your board resets or the DAC glitches, the PLC sees a level jump and you’re now the person who tripped the site. Anything you put in that loop must keep the PLC’s reading correct even when your side is dead.
Two things I’ve used, both off the shelf:
- A loop-powered signal splitter/isolator, one input, two isolated 4-20 mA outputs. The PLC gets one output, your node the other. Loop-powered ones take the power from the loop, so check the voltage budget: supply minus transmitter minimum minus the isolator’s drop minus the PLC input’s burden must stay positive at 20 mA.
- Aisha’s idea done safely: a small series shunt with an isolated analog input module that has Modbus RTU output. The isolation removes the ground loop Ciarán worried about. Your gateway reads it over RS485 like any other Modbus device. Same voltage budget check, a 50 ohm shunt costs 1 V at 20 mA.
I prefer 1 because the PLC’s signal path is a certified part and not a shunt someone may remove later.
Then in Hermetry send the raw mA and convert there. In the root flow, a Scale block before Save telemetry maps 4-20 to your tank range, and you keep the raw value for fault finding (below 3.8 mA means a broken loop):
Scale: in_min 4, in_max 20, out_min 0, out_max 100 -> level_pct
The derived value example is on Derived values.
Aisha has it. Hermetry stores a body that isn’t JSON as a single raw key, which is why you see your template text. Build the JSON with printf, and send all sites in one message, cheaper and tidier:
#!/bin/bash
json="{"
while read -r ip name; do
if ping -c 3 -W 2 "$ip" > /dev/null 2>&1; then ok=1; else ok=0; fi
json+=$(printf '"%s.up":%d,' "$name" "$ok")
done < /home/pi/sites.txt
json="${json%,}}"
mosquitto_pub -h "$BROKER" -p 8883 --capath /etc/ssl/certs -u "$TOKEN" -q 1
-t v1/telemetry -m "$json"
Three pings, not one: counts as up if any one comes back.
For the alert, an alarm rule on the profile per site key with a count debounce, so it needs several bad readings in a row before it opens and several good ones to clear:
{ "type": "pump3_unreachable",
"title": "Pump 3 modem not answering",
"severities": [ { "severity": "MAJOR", "condition": { "key": "pump3.up", "op": "==", "value": 0 } } ],
"debounce": { "mode": "count", "raise": 5, "clear": 2 },
"enabled": true }
Five minutes of failed pings before anyone hears about it. Add a Device offline rule for the Pi too, otherwise a dead Pi looks like “everything fine”. Debounce options: Debounce, hysteresis and dampening.

