This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm whether you accept or reject these cookies being set.

A cookie will be stored in your browser regardless of choice to prevent you being asked this question again. You will be able to change your cookie settings at any time using the link in the footer.

Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
old gauges
#1
Good Afternoon, 
I am new to all of this so please bare with me. I am putting together my materials etc for adding an RP13 based openplotter system in my 1985 Raider 33. The only working electronics on the boat currently are the depth gauge and I will be getting the speed sensor working once I get the barnacles off. I would like to continue to use the old B&G displays but also want them to be input into the RPi so I can set up my handheld HUD and in-cabin screen. So my question is how can I either output data from the RPi to the B&G displays or can I just pigtail the wires from the sensors and directly input them to the displays and the RPi? ANy information would be greatly appreciated. I have a couple of months before she goes back in the water so I have a little time to work all of this out.  Thanks.
1985 Raider 33 resting in the Hudson Valley, NY 
Reply
#2
It really depends on what protocol your B&G instruments, and you sensors, talk among themselves. Some more recent depth and water speed sensors output standard protocols like NMEA0183 or NMEA2000. Older ones typically are paired with the instrument. For example, my old Raymarine ST60 depth and water speed display needs the sensors plugged directly into the back of the display, and it handles depth and speed detection. I then have a SeaTalk output from the display that needs to be converted to NMEA0183 before it can go into OpenPlotter.

If you are willing to hack up some hardware and software, the speed sensor could probably be connected directly to an Arduino or ESP32 device to detect the pulses from the sensor and convert that to NMEA0183 or even SignalK. I might even have some code around here to do that. Depth is another issue - there's usually some pretty sophisticated software in the instrument that controls the sonar output pings and interprets the echos to calculate depth.

Between the B&G displays, they use "B&G Network Protocol" to communicate between systems. As far as I know, it isn't compatible with anything you can do on OpenPlotter without a translation box.

Perhaps if you can tell us what version of B&G displays you have, we might be able to help find a way to interface to them without breaking the bank.
Reply
#3
Hello,

I'd like to revive this ancient thread if I may.

OP, did you ever manage to do this? I am in a similar situation. I have an old installation of Stowe Marine wind instruments. The mast head unit has started to fail, and there is no chance of obtaining spares or repairing it. However the original displays are in excellent health, correct period for the boat, and just look great!

What I'd like to achieve is a simple DAC device that can proble the Signal K server for the apparent wind angle, and then output an analogue voltage that corresponds to that wind angle.

The original completely analogue system uses 3 (12v) voltages phased 120 degrees apart to control the pointer. The display sums the 3 phases together and the result is where the pointer points.

So I'd need to emulate this if possible.... As the value of the apparent wind changes, then the 3 analogue outputs would need to change. Each output swinging between 0v and 12v depending on direction.... but 120 degrees apart.

Is this even possible? I assume I'd need a 3 channel DAC capable of outputting 0v to 12v (but 0v to 10v would probably still drive the Stowe pointer). If anyone can suggest some hardware I should consider, or even if anyone has done this before I'd be very grateful.

I'm a novice at this really, but I love a project, and hate throwing things away if I can save them!

There is also the question of the wind speed.... a pulse from a hall effect sensor in the mast head unit, but we can cross that bridge later.

Any help gratefully appreciated....
Reply
#4
I have managed to drive my old Stowe analogue wind direction instrument from my PI5 using 2x Gravity: 2 Channel I2C DAC Module, 0-10V - DFR0971

I have a basic script that subscribes to SignalK, converts the apparent wind angle in Radians, to 0-10v signals that are phased apart by 120degree and 240degrees. It adds a bit of smoothing and averaging to iron out any spikes in the wind data too.

As the direction changes, so does the needle on the dial. It's only bench tested so far with simulated data, but it appears to work.

I've still to get the wind speed bit working, but I'm looking at that next.... Here is my script.

#!/usr/bin/env python3
import json
import time
import math
import websocket
from DFRobot_GP8403 import DFRobot_GP8403, OUTPUT_RANGE_10V

# --- CONFIG ---
SIGNALK_URL = "ws://localhost:3000/signalk/v1/stream" # Change if server not local
SIGNALK_PATH = "environment.wind.angleApparent"
UPDATE_RATE = 0.1 # 10Hz

# DAC addresses
dac1 = DFRobot_GP8403(0x5B) # Phase A+B
dac2 = DFRobot_GP8403(0x5F) # Phase C

def init_dac(dac, name):
for _ in range(3):
try:
if dac.begin() == 0:
dac.set_DAC_outrange(OUTPUT_RANGE_10V)
print(f"{name} ready")
return True
except Exception as e:
print(f"{name} init failed: {e}")
time.sleep(1)
return False

init_dac(dac1, "DAC1 (0x5B)")
init_dac(dac2, "DAC2 (0x5F)")

# Voltage scaling
Vmax = 10000 # mV full scale

# --- SMOOTHING CONFIG ---
EASE_FACTOR = 0.9 # 0..1, smaller = smoother/easier easing

# Keep current eased values for each phase
current_phase = {"A": 0, "B": 0, "C": 0}

def toMv(angle_rad, phase):
"""Map radians (0..2π) into 0..10V triangle shape with easing"""
angle = angle_rad % (2*math.pi)
if angle <= math.pi:
target = (angle / math.pi) * Vmax
else:
target = ((2*math.pi - angle) / math.pi) * Vmax

# Easing toward the target
current_phase[phase] += (target - current_phase[phase]) * EASE_FACTOR
return int(current_phase[phase])

def handle_angle(angle):
# Phase shifts: A=0°, B=+120°, C=+240°
vA = toMv(angle, "A")
vB = toMv(angle + 2*math.pi/3, "B")
vC = toMv(angle + 4*math.pi/3, "C")

dac1.set_DAC_out_voltage(vA, 0) # Phase A on DAC1 CH0
dac1.set_DAC_out_voltage(vB, 1) # Phase B on DAC1 CH1
dac2.set_DAC_out_voltage(vC, 0) # Phase C on DAC2 CH0


# print(f"A={vA}mV B={vB}mV C={vC}mV")

def on_message(ws, message):
try:
data = json.loads(message)
if "updates" in data:
for u in data["updates"]:
if "values" in u:
for v in u["values"]:
if v.get("path") == SIGNALK_PATH:
angle_rad = v.get("value")
if angle_rad is not None:
handle_angle(float(angle_rad))
except Exception as e:
print(f"Error parsing message: {e}")

def on_open(ws):
print("Connected to Signal K")
sub = {
"context": "vessels.self",
"subscribe": [{"path": SIGNALK_PATH, "period": int(UPDATE_RATE*1000)}]
}
ws.send(json.dumps(sub))

if __name__ == "__main__":
ws = websocket.WebSocketApp(
SIGNALK_URL,
on_open=on_open,
on_message=on_message
)
ws.run_forever()
Reply
#5
(2025-10-02, 02:14 PM)MrGorsky Wrote: I have managed to drive my old Stowe analogue wind direction instrument from my PI5 using 2x Gravity: 2 Channel I2C DAC Module, 0-10V -  DFR0971

I have a basic script that subscribes to SignalK, converts the apparent wind angle in Radians, to 0-10v signals that are phased apart by 120degree and 240degrees.  It adds a bit of smoothing and averaging to iron out any spikes in the wind data too.

As the direction changes, so does the needle on the dial.  It's only bench tested so far with simulated data, but it appears to work.

I've still to get the wind speed bit working, but I'm looking at that next....  Here is my script.

Yeah i contemplated that too, the issue i had was the cost of gauges and drilling holes on my dashboard. So i instead used the DashboardSK Plugin and just put the gauges on my Opencpn screen as i have lots of real estate (17" screen). in my case i was replicating Oil Pressure and Temperature from two engines up to flybridge. So four gauges. (When they fitted the engines they fitted full gauges downstairs where i never drive).   My Setup is 4X INA219s on a ESP32 which read the Engine Senders, digitise it to SK, then i could follow what you have here and go back fro Digital to Volts in gauges.  Nice thinking, i totally get your strategy!  I have also fed a RS485 modbus Wind Speed ad Direction Sensor into SK via an Elfin EW11. Below is the test dashboard of engine gauges and the wind sensors .  I am working on Nodred equations to convert the read values from INA219s back to PSI and Celsius


Attached Files Image(s)
       
Reply
#6
My eventual hope is to have the analogue guages up top in the cockpit as they have always been (work at night, work in sunlight) and then a modern screen below running KIP with all the various options available there. I might even get some sensors on the engine and the batteries like so many seem to have done.

I've managed to get the wind speed working, m/s converted to knots, and then a multiplier of 0.4333 to give the correct readout on the Stowe display. Problem is after I re-built my Pi to "start afresh" it's broken again! Not sure what's going on, but I think I shouldn't have updated the PI with the latest Debian updates maybe? I dunno. I'll get to the bottom of it eventually.

One of the things I can't make work is the pulse inputs to the GPIO pins. I was hoping to wire up the old paddle wheel log so I can get speed through the water and maybe distance data in to Signal K but nothing I try seems to make it pick up the signals. Does the pulse tab on the GPIO app even work???
Reply
#7
(2025-10-08, 09:54 PM)MrGorsky Wrote: Problem is after I re-built my Pi to "start afresh" it's broken again!  Not sure what's going on, but I think I shouldn't have updated the PI with the latest Debian updates maybe? 

What model Pi is it? I have had issues with RPI 5s. I think the 4B is much more reliable
Reply
#8
It is a 5. Brand new from the Pi Hut.
Reply
#9
I've managed to make this work. I tried all sorts, External ESP32 chips, GPIO pin pulses, Node Red. But the best result I could get was by just having two Python scripts running (set to start on boot and keep going forever) that query the Signal K server and either output the wind speed (pulses) GPIO 16, or the wind direction via 2 DACs that convert the SignalK radians to 3 phases of 0-10v to drive the analog needle. Both have some clever maths to make the digital outputs behave like the old school analog sensors, and it works! I need to tidy a few things up and make up a couple of boards to house the optocoupler and OpAmp circuits, but otherwise I'm pretty happy with the result.

Script for the Wind Speed...

#!/usr/bin/env python3
"""
Stowe emulator: read Signal K websocket and pulse GPIO16.
- Uses 0.45 Hz per knot (user-confirmed).
- Pulses are produced by briefly setting GPIO HIGH (LED ON => opto transistor conducts).
- Idle state is LOW.
"""

import json
import time
import threading
import websocket
import gpiod

# ---- CONFIG ----
SIGNALK_URL = "ws://localhost:3000/signalk/v1/stream"
GPIO_CHIP = "gpiochip0"
GPIO_PIN = 16 # BCM 16, as you selected
HZ_PER_KNOT = 0.45 # confirmed multiplier
MAX_PULSE_FRACTION = 0.20 # pulse width <= 20% of period
MAX_PULSE_WIDTH = 0.01 # 10 ms max pulse at low speeds
DISPLAY_INTERVAL = 1.0 # seconds between status prints

# ---- GPIO SETUP ----
chip = gpiod.Chip(GPIO_CHIP)
line = chip.get_line(GPIO_PIN)
# Idle LOW (optocoupler off). Pulse -> set HIGH briefly.
line.request(consumer="stowe_signalk_emulator", type=gpiod.LINE_REQ_DIR_OUT, default_vals=[0])

# ---- Shared state ----
pulse_frequency = 0.0
pulse_width = MAX_PULSE_WIDTH
stop_flag = False
_last_display = 0.0

# ---- PULSE GENERATOR THREAD ----
def pulse_generator():
global pulse_frequency, pulse_width, stop_flag
while not stop_flag:
if pulse_frequency <= 0.0:
time.sleep(0.1)
continue

period = 1.0 / pulse_frequency
# keep pulses reasonably short: min(MAX_PULSE_WIDTH, period * fraction)
pulse_width = min(MAX_PULSE_WIDTH, period * MAX_PULSE_FRACTION)

# Produce a single pulse: set HIGH for pulse_width, then LOW for remainder
line.set_value(1) # HIGH -> LED ON -> opto transistor on -> display input pulled LOW
time.sleep(pulse_width)
line.set_value(0) # LOW -> idle
# sleep for rest of period
tail = period - pulse_width
if tail > 0:
time.sleep(tail)

# ---- DISPLAY THREAD ----
def display_thread():
global pulse_frequency, pulse_width, stop_flag
while not stop_flag:
knots = (pulse_frequency / HZ_PER_KNOT) if HZ_PER_KNOT != 0 else 0.0
print(f"Wind: {knots:.2f} kts | Freq: {pulse_frequency:.2f} Hz | Pulse: {pulse_width*1000:.2f} ms")
time.sleep(DISPLAY_INTERVAL)

# ---- SIGNAL K CALLBACKS ----
def on_message(ws, message):
global pulse_frequency
try:
data = json.loads(message)
if "updates" in data:
for u in data["updates"]:
for v in u.get("values", []):
# adapt this path if your Signal K uses a different one
if v.get("path") == "environment.wind.speedApparent":
mps = v.get("value")
if isinstance(mps, (int, float)):
knots = mps / 0.514444
pulse_frequency = knots * HZ_PER_KNOT
except Exception:
# ignore malformed messages
pass

def on_error(ws, error):
print("WebSocket error:", error)

def on_close(ws, close_status_code, close_msg):
global stop_flag
stop_flag = True
print("WebSocket closed")

def on_open(ws):
# start pulse generator and display threads
threading.Thread(target=pulse_generator, daemon=True).start()
threading.Thread(target=display_thread, daemon=True).start()

# ---- MAIN ----
if __name__ == "__main__":
print("Starting Stowe SignalK -> GPIO16 emulator (0.45 Hz/knot).")
ws = websocket.WebSocketApp(
SIGNALK_URL,
on_message=on_message,
on_error=on_error,
on_close=on_close,
on_open=on_open
)
try:
ws.run_forever()
except KeyboardInterrupt:
print("Interrupted by user")
finally:
stop_flag = True
line.set_value(0)
print("Stopped and GPIO set LOW.")


Script for the direction...

#!/usr/bin/env python3
"""
Stowe Wind Display Driver — Smooth Physics Simulation
-----------------------------------------------------
Reads apparent wind angle from Signal K (radians),
and outputs three 0–10 V analog phases for a Stowe-style
mechanical wind direction display, with realistic "needle inertia".
"""

import json
import time
import math
import websocket
import threading
from DFRobot_GP8403 import DFRobot_GP8403, OUTPUT_RANGE_10V

# --- CONFIG ---
SIGNALK_URL = "ws://localhost:3000/signalk/v1/stream"
SIGNALK_PATH = "environment.wind.angleApparent"
UPDATE_RATE = 0.05 # 20 Hz physics update (50 ms)
ALPHA = 0.15 # EMA smoothing for target angle
STALE_TIMEOUT = 1.0 # seconds before we consider Signal K stale
Vmax = 10000 # mV (10 V full-scale)

# --- DAC SETUP ---
dac1 = DFRobot_GP8403(0x5B) # Phase A+B
dac2 = DFRobot_GP8403(0x5F) # Phase C

def init_dac(dac, name):
"""Initialize a DAC and set it to 0–10 V mode."""
for _ in range(3):
try:
if dac.begin() == 0:
dac.set_DAC_outrange(OUTPUT_RANGE_10V)
print(f"{name} ready (0–10 V mode)")
return True
except Exception as e:
print(f"{name} init failed: {e}")
time.sleep(1)
return False

init_dac(dac1, "DAC1 (0x5B)")
init_dac(dac2, "DAC2 (0x5F)")

# --- NEEDLE PHYSICS PARAMETERS ---
DAMPING = 1.0 # friction (higher = smoother, less jitter)
STIFFNESS = 4.0 # spring force
MASS = 0.5 # inertia (higher = slower, heavier)
DT = UPDATE_RATE # physics time step (s)

# --- SIMULATION STATE ---
theta = 0.0 # current simulated angle (radians)
omega = 0.0 # angular velocity
target_theta = 0.0 # latest target from Signal K
last_data_time = 0 # timestamp of last Signal K update

# --- UTILITY FUNCTIONS ---
def shortest_angle_diff(a, b):
"""Return smallest signed difference (radians) from a → b."""
diff = (b - a + math.pi) % (2 * math.pi) - math.pi
return diff

def to_mv(angle_rad):
"""Map radians (0..2π) to a 0..10 V triangle wave."""
angle = angle_rad % (2 * math.pi)
if angle <= math.pi:
return int((angle / math.pi) * Vmax)
else:
return int(((2 * math.pi - angle) / math.pi) * Vmax)

# --- MAIN PHYSICS LOOP ---
def physics_loop():
"""Run at fixed rate, update DACs smoothly regardless of message rate."""
global theta, omega, target_theta, last_data_time
while True:
now = time.time()

# If Signal K is stale, lightly increase damping (simulate friction)
stale = (now - last_data_time) > STALE_TIMEOUT
damping = DAMPING * (1.5 if stale else 1.0)

# spring-damper physics
diff = shortest_angle_diff(theta, target_theta)
force = diff * STIFFNESS - omega * damping
omega += (force / MASS) * DT
theta += omega * DT
theta %= 2 * math.pi

# Generate 3-phase analog outputs
vA = to_mv(theta)
vB = to_mv(theta + 2 * math.pi / 3)
vC = to_mv(theta + 4 * math.pi / 3)

dac1.set_DAC_out_voltage(vA, 0)
dac1.set_DAC_out_voltage(vB, 1)
dac2.set_DAC_out_voltage(vC, 0)

time.sleep(DT)

# --- SIGNAL K CALLBACKS ---
def on_message(ws, message):
"""Handle incoming Signal K messages."""
global target_theta, last_data_time
try:
data = json.loads(message)
if "updates" in data:
for update in data["updates"]:
for val in update.get("values", []):
if val.get("path") == SIGNALK_PATH:
angle = val.get("value")
if angle is not None:
# EMA smoothing of incoming angle
a = float(angle)
dtheta = shortest_angle_diff(target_theta, a)
target_theta = (target_theta + ALPHA * dtheta) % (2 * math.pi)
last_data_time = time.time()
except Exception as e:
print(f"Message error: {e}")

def on_open(ws):
print("Connected to Signal K server.")
sub = {
"context": "vessels.self",
"subscribe": [{"path": SIGNALK_PATH, "period": int(UPDATE_RATE * 1000)}]
}
ws.send(json.dumps(sub))

# --- MAIN ENTRY POINT ---
if __name__ == "__main__":
print("Starting Stowe wind display client (smooth physics, EMA, wrap-around)...")
threading.Thread(target=physics_loop, daemon=True).start()

ws = websocket.WebSocketApp(
SIGNALK_URL,
on_open=on_open,
on_message=on_message
)
ws.run_forever()
Reply
#10
Just wanted to add that this is up and running now. I have full wind direction and speed from SignalK to my old analogue Stowe wind display, and also wind speed. I think it would work on any pre NMEA wind displays like the old B&G displays etc. No OpAmps needed, the 0-10v I get from the DFRobot DACs I used is more than enough to drive the needle. I also opted for tiny transistor circuits to change the 0-5v logic of the Stowe displays to 0-3.3v logic of the Pi GPIO pins.

In other news, I have also managed to get a speed through the water output from my old Stowe paddle wheel log in to SignalK via Node Red so my signalK server now has a reliable STW input as well.

Next up will be getting the Navsounder depth transducer to play nice with SignalK..... I have probed the display outputs to the optional repeater display and can see some data on the lines, but I haven't had time to do some real testing and recording what's on there yet. I shall.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)