Hi Luxonis Team,

How do I assign a static IP to a OAK-1 POE device other than the default static IP 169.254.1.222?
I do not see a way to do so in the docs: https://docs.luxonis.com/en/latest/pages/tutorials/getting-started-with-poe/#poe-troubleshooting

My situation is that different customers use different classes of IP ranges so manually assigning a static IP to each OAK-1 POE device to work on each network will help. Not all customers will have DHCP. Your guidance is appreciated!

Regards,
Gautam

I did a bit more searching and found an article for how to update the device bootloader configurations. https://docs.luxonis.com/projects/api/en/latest/samples/bootloader/bootloader_config/

"python3 examples/bootloader_config.py read" allows the following JSON to be used to set a static IP.

{'appMem': -1, 'network': {'ipv4': 0, 'ipv4Dns': 0, 'ipv4DnsAlt': 0, 'ipv4Gateway': 0, 'ipv4Mask': 0, 'ipv6': [0, 0, 0, 0], 'ipv6Dns': [0, 0, 0, 0], 'ipv6DnsAlt': [0, 0, 0, 0], 'ipv6Gateway': [0, 0, 0, 0], 'ipv6Prefix': 0, 'mac': [0, 0, 0, 0, 0, 0], 'staticIpv4': False, 'staticIpv6': False, 'timeoutMs': 30000}, 'usb': {'maxUsbSpeed': 3, 'pid': 63036, 'timeoutMs': 3000, 'vid': 999}}

But when I run "python3 bootloader_config.py read" I get the error below:

Found device with name: 172.16.15.17
Current flashed configuration
Traceback (most recent call last):
File "/home/delphidisplay/Desktop/depthai-python/examples/bootloader/bootloader_config.py", line 37, in <module>
print(f'{bl.readConfigData()}')
RuntimeError: Bootloader version 0.0.14 required to send request 'GetBootloaderConfig'. Current version 0.0.12

How do I upgrade the bootloader version?

  • erik replied to this.

    Hello gbanuru , could you try reading the bootloader config first, that way it should be in the latest format? The bootloader config example should support reading the config as well.
    Thanks, Erik

      Hi erik I'm not sure what you mean - are you asking to:

      1. run "python3 examples/bootloader/flash_bootloader.py"
      2. run "python3 examples/bootloader/bootloader_config.py flash"
      3. run "python3 examples/bootloader/bootloader_config.py flash flash.json" where flash.json is a file with the contents:
        {'appMem': -1, 'network': {'ipv4': 0, 'ipv4Dns': 0, 'ipv4DnsAlt': 0, 'ipv4Gateway': 0, 'ipv4Mask': 0, 'ipv6': [0, 0, 0, 0], 'ipv6Dns': [0, 0, 0, 0], 'ipv6DnsAlt': [0, 0, 0, 0], 'ipv6Gateway': [0, 0, 0, 0], 'ipv6Prefix': 0, 'mac': [0, 0, 0, 0, 0, 0], 'staticIpv4': False, 'staticIpv6': False, 'timeoutMs': 30000}, 'usb': {'maxUsbSpeed': 3, 'pid': 63036, 'timeoutMs': 3000, 'vid': 999}}

      Thanks,
      Gautam

      • erik replied to this.

        Hello gbanuru , just noticed the same thing, the JSON isn't valid (opened PR, here). Could you try with this json/bootloader config?

        {
            "appMem": -1,
            "network": {
                "ipv4": 0,
                "ipv4Dns": 0,
                "ipv4DnsAlt": 0,
                "ipv4Gateway": 0,
                "ipv4Mask": 0,
                "ipv6": [
                    0,
                    0,
                    0,
                    0
                ],
                "ipv6Dns": [
                    0,
                    0,
                    0,
                    0
                ],
                "ipv6DnsAlt": [
                    0,
                    0,
                    0,
                    0
                ],
                "ipv6Gateway": [
                    0,
                    0,
                    0,
                    0
                ],
                "ipv6Prefix": 0,
                "mac": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ],
                "staticIpv4": false,
                "staticIpv6": false,
                "timeoutMs": 30000
            },
            "usb": {
                "maxUsbSpeed": 3,
                "pid": 63036,
                "timeoutMs": 3000,
                "vid": 999
            }
        }

          Hi erik

          I tried the following below given the branch update, but the script just stalls:

          (venv) gautam@MacBook-Pro bootloader (bootloader_config_read_json) $ python3 bootloader_config.py read
          Found device with name: 14442C1081CC0FD100-ma2480
          Current flashed configuration
          null
          (venv) gautam@MacBook-Pro bootloader (bootloader_config_read_json) $ python3 bootloader_config.py flash config.json
          Found device with name: 14442C1081CC0FD100-ma2480

          The script stalls at the following line: (success, error) = bl.flashConfigFile(path) in bootloader_config.py. I am using config.json which has the JSON described above. I can test on a POE device in the afternoon, this was run on USB.

          Regards,
          Gautam

          • erik replied to this.

            gbanuru which device were you using? Is it an OAK-D-IOT?

            Hi @erik ,

            I have an OAK-D and 2 OAK-1 POEs. I want to set up static IPs for the OAK-1 POE.

            • erik replied to this.

              Hi gbanuru , so your previous attempt probably didn't work since you were using OAK-D, where you can't flash the bootloader (there is no flash). Could you re-try with the POE model?
              Thanks, Erik

              Hi @erik,

              Good news, I ran the following commands in examples/bootloader to flash the bootloader with template config.json on my OAK-1 POE.

              python3 flash_bootloader.py # update bootloader to latest version
              python3 bootloader_config.py flash config.json # flashes config.json
              python3 bootloader_config.py read # shows config.json that was flashed
              python3 bootloader_version.py # shows version 0.0.15

              Now I have the following information:
              Static IP to configure - 172.16.15.200
              Subnet: 255.255.255.0
              Gateway: 172.16.15.254

              At this point it looks like I should set the following attributes in config.json:
              "ipv4": "172.16.15.200"
              "ipv4Gateway": "172.16.15.254"
              "ipv4Mask": "255.255.255.0"
              "staticIpv4": true

              Does that look right? Or am I missing something else. Thanks for your help!

              I believe that currently, just passing string won't work. I believe it uses one 32 bit int, so you would need to convert 4x 8bit int into one 32 bit int (eg 1.2.3.4 => 1*2^24 + 2*2^16 + 3*2^8 + 4).

              Hi @erik,

              I flashed the POE device with the new config.json bootloader to set a static IP, and power cycled the POE Camera but do not see the device via ping or dai.getAllAvailableDevices().

              When I try to ping the new IP 172.16.15.201 ping halts and does not even say the host is unreachable. The old IP before flashing was assigned via DHCP as 172.16.15.70 so if I could reach the old IP, I don't see why I couldn't have reached the new one. Do you have any suggestions?

              For reference I created a script below (thanks GitHub CoPilot) to create a new JSON file that was used to flash the device via "python3 bootloader_config.py flash new_config.json". bootloader_config.py said the flash was successful.

              #!/usr/bin/env python3
              
              # import module to read command line arguments
              import argparse
              import json
              import re
              import os
              
              # read json file and return a dictionary
              def read_json(json_file):
                  with open(json_file) as json_data:
                      d = json.load(json_data)
                  return d
              
              # convert ipv4 to 32 bit integer
              def ipv4_to_int(ipv4):
                  field1, field2, field3, field4 = ipv4.split(".")
                  
                  # same as int(field1)*(2**24) + int(field2)*(2**16) + int(field3)*(2**8) + int(field4)
                  return int(field1) << 24 | int(field2) << 16 | int(field3) << 8 | int(field4)
              
              # read file from command line and check if it exists and if it is a .json file
              def read_file(file):
                  if os.path.isfile(file):
                      if file.endswith(".json"):
                          return read_json(file)
                      else:
                          print(f"File {file} is not a .json file")
                          exit(1)
                  else:
                      print(f"File {file} does not exist")
                      exit(1)
              
              # ask for arguments from command line
              def get_args():
                  parser = argparse.ArgumentParser(description="Generate a config file")
                  parser.add_argument("-f", "--file", default="config.json", help="Path to the .json file")
                  parser.add_argument("-o", "--output", help="Path to the output file")
                  args = parser.parse_args()
                  return args
              
              # ask user for input
              def get_input(question, check_ipv4=False, check_bool=False):
                  user_input = input(question).strip()
                  
                  if check_ipv4:
                      while not is_ipv4(user_input):
                          print("Please enter a valid IPv4 address")
                          user_input = input(question).strip()
                  
                  elif check_bool:
                      while user_input not in ["y", "n"]:
                          print("Please enter y or n")
                          user_input = input(question).strip()
                      
                      return user_input == "y"
                  
                  return user_input
              
              # check if string is ipv4
              def is_ipv4(ipv4):
                  return re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ipv4)
              
              def start_configuration():
                  print("Started Bootloader IPv4 JSON Configuration")
              
                  args = get_args()
                  config = read_file(args.file)
                  
                  ipv4 = get_input("Enter the IPv4 Address: ", check_ipv4=True)
                  static = get_input("Is this a static IPv4 Address? (y/n): ", check_bool=True)
                  netmask = get_input("Enter the IPv4 Netmask: ", check_ipv4=True)
                  gateway = get_input("Enter the IPv4 Gateway: ", check_ipv4=True)
                  
                  config["network"]["ipv4"] = ipv4_to_int(ipv4)
                  config["network"]["staticIpv4"] = static
                  config["network"]["ipv4Gateway"] = ipv4_to_int(gateway)
                  config["network"]["ipv4Mask"] = ipv4_to_int(netmask)
                  
                  if args.output:
                      with open(args.output, "w") as outfile:
                          json.dump(config, outfile, indent=4)
                      print(f"JSON Config Saved to {args.output}")
                  else:
                      print("JSON Config Result:")
                      print(json.dumps(config, indent=4))
              
              if __name__ == "__main__":
                  start_configuration()

              The resulting JSON created was below where ipv4 is 172.16.15.201, ipv4Mask is 255.255.255.0, ipv4Gateway is 172.16.15.254, and staticIpv4 was set to true:

              {
                  "appMem": -1,
                  "network": {
                      "ipv4": 2886733769,
                      "ipv4Dns": 0,
                      "ipv4DnsAlt": 0,
                      "ipv4Gateway": 2886733822,
                      "ipv4Mask": 4294967040,
                      "ipv6": [
                          0,
                          0,
                          0,
                          0
                      ],
                      "ipv6Dns": [
                          0,
                          0,
                          0,
                          0
                      ],
                      "ipv6DnsAlt": [
                          0,
                          0,
                          0,
                          0
                      ],
                      "ipv6Gateway": [
                          0,
                          0,
                          0,
                          0
                      ],
                      "ipv6Prefix": 0,
                      "mac": [
                          0,
                          0,
                          0,
                          0,
                          0,
                          0
                      ],
                      "staticIpv4": true,
                      "staticIpv6": false,
                      "timeoutMs": 30000
                  },
                  "usb": {
                      "maxUsbSpeed": 3,
                      "pid": 63036,
                      "timeoutMs": 3000,
                      "vid": 999
                  }
              }
              • erik replied to this.

                Hello gbanuru , I would actually suggest using the API itself (bootloader API), where you can set these configurations eg. ip with the string "123.123.123.123", so you don't soft-brick your device. And we apologize that there isn't such an example yet, we will be adding it soon.

                  Hi erik,

                  Ah wish I saw this sooner. This method setStaticIPv4(std::string ip, std::string mask, std::string gateway) is exactly what I was looking for: https://docs.luxonis.com/projects/api/en/latest/references/python/?highlight=.readConfigData()#depthai.DeviceBootloader.Config.setStaticIPv4.

                  But now I'm a bit afraid the steps I've described thus far may have soft-bricked the device. Is there a way to reset the OAK-1 POE? I do see a Module Reset Button labelled G here although I'm not sure if it is relevant: "https://docs.luxonis.com/projects/hardware/en/latest/pages/SJ2096POE.html"

                  • erik replied to this.

                    Hi erik , Running fix_softbrick.py in the linked announcement worked! Now I can connect to my device.

                    I will wait for an example on how to use the bootloader API for (.setStaticIPv4(ipv4, mask, gateway), .setDynamicIPv4(ipv4, mask, gateway)).

                    I wrote a script to use the bootloader API which shows that the .getIPv4(), .getIPv4Mask(), .getIPv4Gateway(), .isStaticIPV4() are set, but when I rerun the same script I do not see the changes to the bootloader just made.

                    # usr/bin/env/python3
                    
                    import depthai as dai
                    
                    current_ip = input("Enter IP of POE OAK Device: ").strip() # example: 172.16.15.17
                    found_poe, poe = dai.DeviceBase.getDeviceByMxId(current_ip)
                    
                    if not found_poe:
                        print(f"Did not find {current_ip}, exiting.")
                        exit(1)
                    
                    print(f"Found {current_ip}")
                    poebl = dai.DeviceBootloader(poe)
                    device_config = poebl.Config()
                    
                    def see_config():
                    	print("\nCurrent IPv4 Configurations: ")
                    	print(f"IPv4:           {device_config.getIPv4()}")
                    	print(f"IPv4 Mask:      {device_config.getIPv4Mask()}") 
                    	print(f"IPv4 Gateway:   {device_config.getIPv4Gateway()}")
                    	print(f"Is IPv4 static: {device_config.isStaticIPV4()}")
                    
                    def enter_new_config():
                    	print("\nNew IPv4 Configurations: ")
                    	ipv4 = input("Enter IPv4: ").strip()
                    	mask = input("Enter IPv4 Mask: ").strip()
                    	gateway = input("Enter IPv4 Gateway: ").strip()
                    	is_static = True if input("Is this a static IPv4 (sets dynamic IPv4 if no)? (y/n): ").strip() == "y" else False
                    
                    	return ipv4, mask, gateway, is_static
                    
                    def confirm_new_config(ipv4, mask, gateway, is_static):
                    	print("\nEntered IPv4 Configurations: ")
                    	print(f"New IPv4:           {ipv4}")
                    	print(f"New IPv4 Mask:      {mask}") 
                    	print(f"New IPv4 Gateway:   {gateway}")
                    	print(f"Is New IPv4 static: {is_static}")
                    
                    	if is_static:
                    		print("Note: Static IPv4 will not start DHCP client")
                    	else:
                    		print("Note: Dynamic IPv4 will be used (sets IP and starts DHCP client)")
                    
                    	is_confirmed = True if input("\nPlease confirm this Configuration to Set (y/n): ").strip() == "y" else False
                    
                    	if not is_confirmed:
                    		print("New Config is not Confirmed, exiting.")
                    		exit(1)
                    
                    def perform_config(ipv4, mask, gateway, is_static):
                    	print("Setting New Configurations...")
                     	
                    	if is_static:
                     		device_config.setStaticIPv4(ipv4, mask, gateway)
                    	else:
                    		device_config.setDynamicIPv4(ipv4, mask, gateway)
                    	
                    	print("Configuration Complete")
                    
                    def run_config():
                        see_config()
                        ipv4, mask, gateway, is_static = enter_new_config()
                        confirm_new_config(ipv4, mask, gateway, is_static)
                        perform_config(ipv4, mask, gateway, is_static)
                        see_config()
                    
                    if __name__ == "__main__":
                    	run_config()
                      5 days later

                      erik any idea when an example will be added. I'm a newbie on these cameras and any help is appreciated.

                      gbanuru I was able to run your script, but I'm not seeing the updates when I rerun your script.

                      Hi gbanuru
                      Sorry for delay on this.

                      To read out config from device do the following:

                      device_config = poebl.readConfig()

                      Your current code just creates a new empty dai.DeviceBootloader.Config object.

                      To flash the config run the following at the end (just editing the dai.DeviceBootloader.Config object will not "save" the configuration)

                      poebl.flashConfig(device_config)

                      Regards, Martin

                      I just tried the code below and it correctly sets a static IP, which it keeps even after a power reset. Could you try it out? Thanks!

                      import depthai as dai
                      
                      (found, info) = dai.DeviceBootloader.getFirstAvailableDevice()
                      
                      def check_str(s: str):
                          spl = s.split(".")
                          if len(spl) != 4:
                              raise ValueError(f"Entered value {s} doesn't contain 3 dots. Value has to be in the following format: '255.255.255.255'")
                          for num in spl:
                              if 255 < int(num):
                                  raise ValueError("Entered values can't be above 255!")
                          return s
                      
                      if found:
                          print(f'Found device with name: {info.desc.name}');
                          with dai.DeviceBootloader(info) as bl:
                              conf = dai.DeviceBootloader.Config()
                      
                              ipv4 = check_str(input("Enter IPv4: ").strip())
                              mask = check_str(input("Enter IPv4 Mask: ").strip())
                              gateway = check_str(input("Enter IPv4 Gateway: ").strip())
                      
                              val = input(f"Flashing static IPv4 {ipv4}, mask {mask}, gateway {gateway} to the POE device. Enter 'y' to confirm. ").strip()
                              if val != 'y':
                                  raise Exception("Flashing aborted.")
                      
                              conf.setStaticIPv4(ipv4, mask, gateway)
                              (success, error) = bl.flashConfig(conf)
                      
                              if not success:
                                  print(f"Error occured while flashing the boot config: {error}")
                              else:
                                  print(f"Boot config flashed successfully")