Skip to content

Instantly share code, notes, and snippets.

@jasonacox
Last active June 14, 2025 19:44
Show Gist options
  • Save jasonacox/91479957d0605248d7eadb919585616c to your computer and use it in GitHub Desktop.
Save jasonacox/91479957d0605248d7eadb919585616c to your computer and use it in GitHub Desktop.
Set up RaspberryPi as Network Router to Powerwall Gateway

RaspberryPi Bridge - Powerwall Router

This will set up a Raspberry Pi to connect to a Tesla Powerwall Gateway (TEG) and bridge that connection to the ethernet connected LAN.

UPDATE for Powerwall Firmware 25.10.1+

As of Tesla Powerwall Firmware 25.10.1, a local host based static route using the Powerwall LAN IP (ie. sudo ip route add 192.168.91.1 via $POWERWALL_IP) is no longer supported by the Powerwall. Tesla is blocking access via this method. You will need to use a bridge method (as shown below) or have your host direclty connect to the WiFi Access Point on your Powerwall (gateway) to get the extended metrics (vitals).

Below is a method to set up a Raspberry Pi to be a bridge between your LAN and the Powerwall, which will require you to set up a local host baed route (ie. sudo ip route add 192.168.91.1 via $RPI_IP). Alternatively, you can see a method to host pypowerwall on Raspberry Pi that connects to both your LAN and Powerwall. That is described here: jasonacox/Powerwall-Dashboard#607

Network Configuration

 ___________________          __________________________           _______________
[ Powerwall Gateway ]        [  Raspberry Pi (Bridge)   ]         [      Host     ]
[       TEG         ]  WiFi  [__________________________]   LAN   [ Linux/Mac/Win ]
[   WiFi: TEG-xxx   ] <----  [ 192.168.91.x | 10.0.1.55 ] <-----> [   10.0.1.65   ]
[   192.168.91.1    ]        [  WiFi (dhcp) |  Ethernet ]         [      LAN      ]
 ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾         [‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾]          ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
                             [   NAT to 192.168.91.x.   ]
                              ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 

Raspberry Pi

  1. Create or edit /etc/wpa_supplicant/wpa_supplicant.conf:
network={
        ssid="TEG-xxx"
        psk="password"
}
  1. Restart Networking and Test
sudo systemctl restart networking

# Test
ifconfig wlan0
ping -c 1 192.168.91.1
  1. Set up IPv4 Routing and Reboot
# Add IP Forwarding - Uncomment net.ipv4.ip_forward=1
sudo sed -i -e '/^#net\.ipv4\.ip_forward=1/s/^#//' /etc/sysctl.conf
sudo sysctl -w net.ipv4.ip_forward=1

# Restart
sudo reboot
  1. Set up NAT
# The Powerwall will reject IP addresses not in the 192.168.91.x range
# so you will need to set up network address translation.

sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE

# There are various ways to make them persistent, here is one.
sudo apt install iptables-persistent
sudo netfilter-persistent save

Host

On the host, you need to add a route to use the Raspberry Pi as a gateway to get to the Powerwall Gateway.

# Linux
sudo ip r add 192.168.91.0/24 via 10.0.1.55

# MacOS
sudo route add -host 192.168.91.1 10.0.1.55

# Test
ping -c 1 192.168.91.1
curl -ik https://192.168.91.1
@jasonacox
Copy link
Author

I wonder if there is a way to do this with virtual interfaces (e.g. eth0:1 to one and eth0:2 to the other)? I'm not anywhere close to a network guru, but hopefully someone in the community is.

@Nexarian
Copy link

@jasonacox Virtual interfaces might work, but I was trying to avoid needing to trigger a large-scale refactor of every use of requests.get or requests.post in all of pypowerwall/dashboard. Ergo: I was simply hoping I could create a virtual IP (like 192.168.92.100/101) and then simply redirect pypowerwall to that using the host parameter.

If you're open to that (adding the parameters that set the interface and/or the source address to the system) that is probably MORE likely to work, but it's a much bigger refactor than the simple ones I've done thus far.

@Nexarian
Copy link

Nexarian commented Jan 5, 2025

@jasonacox I have a fix for this.

Status

The script is still a bit rough/immature. I'm working on figuring out how to make it better and/or integrate it into the server. I also have other variations that work using network namespaces and virtual interfaces, but those seem unnecessary.

Summary

Essentially: Instead of calling 192.168.91.1 directly, set the host in pypowerwall to one of [192.168.92.100, 192.168.92.101] (Subnet choice is arbitrary). Currently my inverters are at [192.168.1.67 , 192.168.92.250] and my raspberry pi is at 192.168.1.225

Steps

  1. Create virtual IP address
  2. Add static route in a unique table
  3. Add DNAT destination rerouting to convert target IP to Tesla internal IP in the OUTPUT (NOT PREROUTING) table.
  4. Add a mangle table entry to MARK anything coming from the virtual IP
  5. Add a rule that anything marked should use the unique table above.
  6. Finally, add a postrouting rule so that this crazy request can find its way back.

This is the reason for this fix that I posted earlier today. Most of TEDAPI worked except firmware version retrieval!

I will update my other PR with this.

Code

Routing Configuration
#!/bin/bash

set -ex

# Function to check if a command succeeded
check_command() {
    if [ $? -ne 0 ]; then
        echo "Error: $1"
        exit 1
    fi
}

# Function to clean up existing rules and configurations
cleanup() {
    echo "Cleaning up existing rules and configurations..."
    
    # Remove virtual IP addresses
    ip addr del 192.168.92.100/24 dev eth0 2>/dev/null || true
    ip addr del 192.168.92.101/24 dev eth0 2>/dev/null || true

    # Flush NAT table
    iptables -t nat -F
    check_command "Failed to flush NAT table"

    # Flush mangle table
    iptables -t mangle -F
    check_command "Failed to flush mangle table"

    # Remove all rules in filter table
    iptables -F
    check_command "Failed to flush filter table"

    # Remove non-default chains
    iptables -X
    check_command "Failed to delete non-default chains"

    # Remove all ip rules (except default)
    ip rule show | grep -v "from all lookup" | cut -d: -f1 | xargs -r -n1 ip rule del prio
    check_command "Failed to remove ip rules"

    # Remove routing tables
    ip route flush table 100 || true
    ip route flush table 101 || true
    check_command "Failed to flush routing tables"

    echo "Cleanup completed successfully."
}

# Main setup function
setup() {
    echo "Setting up routing and NAT rules..."

    # Enable IP forwarding
    echo 1 > /proc/sys/net/ipv4/ip_forward
    check_command "Failed to enable IP forwarding"

    # Add virtual IP addresses
    ip addr add 192.168.92.100/24 dev eth0
    check_command "Failed to add virtual IP 192.168.92.100"
    ip addr add 192.168.92.101/24 dev eth0
    check_command "Failed to add virtual IP 192.168.92.101"

    # Set up routing tables
    ip route add 192.168.91.0/24 via 192.168.1.67 dev eth0 onlink table 100
    check_command "Failed to add route to table 100"
    ip route add 192.168.91.0/24 via 192.168.1.250 dev eth0 onlink table 101
    check_command "Failed to add route to table 101"

    # Set up NAT rules
    iptables -t nat -A OUTPUT -d 192.168.92.100 -j DNAT --to-destination 192.168.91.1
    check_command "Failed to add DNAT rule for 192.168.92.100"
    iptables -t nat -A OUTPUT -d 192.168.92.101 -j DNAT --to-destination 192.168.91.1
    check_command "Failed to add DNAT rule for 192.168.92.101"

    iptables -t nat -A PREROUTING -d 192.168.92.100 -j DNAT --to-destination 192.168.91.1
    check_command "Failed to add DNAT rule for 192.168.92.100"
    iptables -t nat -A PREROUTING -d 192.168.92.101 -j DNAT --to-destination 192.168.91.1
    check_command "Failed to add DNAT rule for 192.168.92.101"

    # Set up packet marking
    iptables -t mangle -A OUTPUT -d 192.168.92.100 -j MARK --set-mark 1
    check_command "Failed to add mark for 192.168.92.100"
    iptables -t mangle -A OUTPUT -d 192.168.92.101 -j MARK --set-mark 2
    check_command "Failed to add mark for 192.168.92.101"

    # Set up ip rules
    ip rule add fwmark 1 table 100
    check_command "Failed to add ip rule for mark 1"
    ip rule add fwmark 2 table 101
    check_command "Failed to add ip rule for mark 2"

    # Set up return traffic NAT
    iptables -t nat -A POSTROUTING -s 192.168.92.100 -d 192.168.91.1 -j SNAT --to-source 192.168.1.225
    check_command "Failed to add return SNAT rule for 192.168.92.100"
    iptables -t nat -A POSTROUTING -s 192.168.92.101 -d 192.168.91.1 -j SNAT --to-source 192.168.1.225
    check_command "Failed to add return SNAT rule for 192.168.92.101"

    echo "Setup completed successfully."
}

# Main execution
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root" 
   exit 1
fi

# Cleanup first
cleanup

# Then setup
setup

echo "All operations completed successfully."

@jasonacox
Copy link
Author

Thanks @Nexarian !

@billraff
Copy link

billraff commented May 12, 2025

My PW was updated to 25.2.0 last night and so I see I am no longer getting data in the dashboard. I ran setup to use option 2 for Cloud and data is now being recorded. I'd like to get back to having vitals in the dashboard. I have a RPi4 that was collecting dust in my closet so moved into the garage to be closer to the gateway and the TEG access point. I followed successfully steps 1-3 above and am ready to proceed to modifying the Host. My PW Dashboard is running in Docker on my Synology. In the command -

Linux

sudo ip r add 192.168.91.0/24 via 10.0.1.55

what exactly is happening? What does the via 10.0.1.55 indicate? Since I don't know what that ip refers to I'm a little gun shy to pull the trigger. Thanks for any help.

Edit:

So went ahead and set everything up (even the last step). I'm having issues getting logged into the gateway though when I try option 1 in the setup. It fails and I have to revert to option 2 (cloud). When I connect to the gateway when I have my iPad on the TEG-xx network (192.168.91.1) I get the familiar login and I can log in using last 5 of the gateway password and my email address. But in the setup.sh I've tried the full 10 letters, last 5 letters and it always fails. The login at the gateway no longer has the installer option for me, only customer.

@jasonacox
Copy link
Author

jasonacox commented May 13, 2025

10.0.1.55

This is a bogus placeholder representing the local IP address (LAN) of your Powerwall gateway. If you don't know what it is, you can scan for it:

# install pypowerwall library
pip install pypowerwall

# scan
python -m pypowerwall scan

You can then use that Ip address to create the static route (replace 10.0.1.55 with the address of your Powerwall). You use use option 1 or 4 in setup to access the extended metrics.

Now for the bad news. As of Tesla Powerwall Firmware 25.10.1, the static route we are trying above no longer works. Tesla is blocking access via that method. You will need to direclty connect to the WiFi Access Point on your Powerwall to get the extended metrics (vitals). Here is a way to set up a RPi to do that: jasonacox/Powerwall-Dashboard#607

Good luck!

@billraff
Copy link

Thanks for the help Jason. Guess I thought using the above was a work around to losing the static route since the description made it seem so to my thinking. Not the first time I've been down a dead end. I started using your wonderful Dashboard on a RPi4 and then moved to the NAS. Guess it's one step forward two steps back.

@jasonacox
Copy link
Author

Yes, Tesla makes it... interesting. 😉

This discussion was started when the routing was allowed (and still is until you hit the 25.10.1 upgrade). I'll edit the top to note that.

@jasonacox
Copy link
Author

Community members are reporting using device like https://www.amazon.com/dp/B09N72FMH5?th=1 to help provide the bridge function mentioned in this GIST (instead of using a RPi). See instructions in jasonacox/Powerwall-Dashboard#109 (comment) by @mccahan.

@NikolayActionEngine
Copy link

Thank you Jason & members who reported it, step #4 (Setup NAT) in Raspberry Pi setup is mandatory: https://gist.github.com/jasonacox/91479957d0605248d7eadb919585616c#raspberry-pi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment