import subprocess import sys import asyncio import os async def run_remote_command(remote_host, command): ssh_command = ['ssh', f'root@{remote_host}', command] process = await asyncio.create_subprocess_exec( *ssh_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await process.communicate() stdout_decoded = stdout.decode() stderr_decoded = stderr.decode() if process.returncode == 0: return stdout_decoded + stderr_decoded else: return f"Failed to run the command: {command}, error: {stderr_decoded}" async def check_GX_type(remote_host): command = "cat /etc/venus/machine" return await run_remote_command(remote_host, command) async def resize(remote_host): command = "sh /opt/victronenergy/swupdate-scripts/resize2fs.sh" return await run_remote_command(remote_host, command) async def update_cerbo_firmware(remote_host): command = "sh /opt/victronenergy/swupdate-scripts/check-updates.sh -swu http://updates.victronenergy.com/feeds/venus/release/images/einstein/venus-swu-einstein-20240523125018-v3.32.swu" return await run_remote_command(remote_host, command) async def set_official_update_feed(remote_host): command = "dbus -y com.victronenergy.settings /Settings/System/ReleaseType SetValue %0" return await run_remote_command(remote_host, command) async def enable_large_image(remote_host): command = "dbus -y com.victronenergy.settings /Settings/System/ImageType SetValue %1" return await run_remote_command(remote_host, command) async def check_large_image_or_not(remote_host): command = "dbus -y com.victronenergy.settings /Settings/System/ImageType GetValue" return await run_remote_command(remote_host, command) async def update_firmware(remote_host): command = "sh /opt/victronenergy/swupdate-scripts/check-updates.sh -update" return await run_remote_command(remote_host, command) async def enable_NodeRed(remote_host): command = "dbus -y com.victronenergy.platform /Services/NodeRed/Mode SetValue %1" return await run_remote_command(remote_host, command) async def disable_NodeRed(remote_host): command = "dbus -y com.victronenergy.platform /Services/NodeRed/Mode SetValue %0" return await run_remote_command(remote_host, command) async def check_NodeRed_enabled_or_not(remote_host): command = "dbus -y com.victronenergy.platform /Services/NodeRed/Mode GetValue" return await run_remote_command(remote_host, command) async def download_node_red_dashboard(remote_host): change_dir_command = "cd /data/home/nodered/.node-red" install_command = "npm install --no-audit --no-update-notifier --no-fund --save --save-prefix=~ --production --engine-strict node-red-dashboard@latest" command = f"{change_dir_command} && {install_command}" return await run_remote_command(remote_host, command) async def disable_BMS_Controlling_MPPT(remote_host): command = "dbus -y com.victronenergy.solarcharger /Settings/BmsPresent SetValue %0" return await run_remote_command(remote_host, command) async def check_mkVersion(remote_host): command = "dbus -y com.victronenergy.vebus.ttyS4 /Interfaces/Mk2/Version GetValue" return await run_remote_command(remote_host, command) async def check_allow_mkVersion_update_or_not(remote_host): command = "dbus -y com.victronenergy.settings /Settings/Vebus/AllowMk3Fw212Update GetValue" return await run_remote_command(remote_host, command) async def update_mkVersion(remote_host): command = "dbus -y com.victronenergy.settings /Settings/Vebus/AllowMk3Fw212Update SetValue %1" return await run_remote_command(remote_host, command) async def import_pika(remote_host): change_dir_command = "cd /data/innovenergy/pika-0.13.1/" install_command = "python3 setup.py install --user" command = f"{change_dir_command} && {install_command}" return await run_remote_command(remote_host, command) async def make_rclocal_executable(remote_host): command = "chmod +x /data/rc.local" return await run_remote_command(remote_host, command) async def reboot(remote_host): command = "reboot" return await run_remote_command(remote_host, command) async def upload_files(remote_host,which_file): if which_file == 1: file_location_mappings = { "rc.local": "/data/", "dbus-fzsonick-48tl": "/data/", "aggregator": "/data/", "service": "/data/innovenergy/openvpn/", "openvpn": "/data/innovenergy/openvpn/", "pika-0.13.1": "/data/innovenergy/" } else: file_location_mappings = { "flows.json": "/data/home/nodered/.node-red/", "settings-user.js": "/data/home/nodered/.node-red/", } cerbo_release_files_folder = os.path.join(os.getcwd(), "CerboReleaseFiles") if not os.path.exists(cerbo_release_files_folder): return "CerboReleaseFiles folder does not exist." try: tasks = [] for file_name, remote_location in file_location_mappings.items(): file_path = os.path.join(cerbo_release_files_folder, file_name) if not os.path.exists(file_path): raise FileNotFoundError(f"File {file_name} not found in {cerbo_release_files_folder}.") command = [ "rsync", "-r", file_path, f"root@{remote_host}:{remote_location}" ] tasks.append(command) # Execute rsync commands asynchronously for task in tasks: subprocess.run(task, check=True) return "All files uploaded successfully." except FileNotFoundError as e: return str(e) except subprocess.CalledProcessError as e: return f"Error occurred while uploading files: {e}" except Exception as e: return f"An error occurred while uploading files: {str(e)}" async def check_connection(remote_host): result = await run_remote_command(remote_host, 'echo Connection successful') return "Connection successful" in result async def main(remote_host): ##### 1. check connection ###### print("Check connection!") if not await check_connection(remote_host): sys.exit("Failed to ssh!") ##################################################### Part 1 ################################################################ gx_type = await check_GX_type(remote_host) if gx_type == "einstein\n": ##### 2. upload VPN and battery files ###### print("Upload VPN and battery files!") if(await upload_files(remote_host,1)!="All files uploaded successfully."): sys.exit("Failed to upload files!") else: print(await upload_files(remote_host,1)) ##### 3. upload VPN and battery files ###### print("Make rc.local executable!") print(await make_rclocal_executable(remote_host)) ##### 4. update firmware with normal image ##### print("Update Cerbo GX firmware now!") print(await update_cerbo_firmware(remote_host)) else: sys.exit("It's not Cerbo GX!") ##################################################### Part 1 ################################################################ # Wait for remote computer to come back online await asyncio.sleep(5) while not await check_connection(remote_host): print("Waiting for remote computer to come back online...") await asyncio.sleep(10) # 10 seconds #################################################### Part 2 ################################################################ #### 5. update firmware with large image #### print("Set update feed to official release now!") print(await set_official_update_feed(remote_host)) print("Enable large image now!") print(await enable_large_image(remote_host)) image_type = await check_large_image_or_not(remote_host) if image_type == "1\n": print("Update firmware with large image!") print(await update_firmware(remote_host)) else: sys.exit("Failed to enable large image!") #################################################### Part 2 ################################################################ # Wait for remote computer to come back online await asyncio.sleep(5) while not await check_connection(remote_host): print("Waiting for remote computer to come back online...") await asyncio.sleep(10) # 10 seconds ##################################################### Part 3 ################################################################ #### 6. resize /dev/root ##### print("Resize /dev/root now!") resize_result = await resize(remote_host) if "Failed" in resize_result: sys.exit(f"Failed to resize: {resize_result}") else: print(resize_result) #### 7. import pika #### print("Import pika!") import_result = await import_pika(remote_host) if "Failed" in import_result: sys.exit(f"Failed to import pika: {import_result}") else: print(import_result) #### 8. udpate to MK3 ##### if (await check_mkVersion(remote_host) == "value = 1170212\n" and await check_allow_mkVersion_update_or_not(remote_host) == "0\n"): print("Update MK3!") print(await update_mkVersion(remote_host)) else: print("No need to update to MK3!") #### 9. enable Node Red ##### print("Enable Node Red now!") print(await enable_NodeRed(remote_host)) if(await check_NodeRed_enabled_or_not(remote_host) == "value = 1\n"): ##### 10. download Node Red Dashboard ##### print("Download Node Red Dashboard now!") print(await download_node_red_dashboard(remote_host)) else: sys.exit("Failed to enable Node Red!") ##### 11. upload files related to Node Red ##### print("Upload files related to Node Red now!") if(await upload_files(remote_host,2)!="All files uploaded successfully."): sys.exit("Failed to upload files!") #### 12. restart Node Red to load and deploy flows ##### print("Disable Node Red!") if(await disable_NodeRed(remote_host) == "retval = 0\n"): print("Re-enable Node Red!") print(await enable_NodeRed(remote_host)) if(await check_NodeRed_enabled_or_not(remote_host) == "value = 1\n"): print("Node Red is set now!") else: sys.exit("Failed to re-enable Node Red!") ##################################################### Part 3 ################################################################ if __name__ == "__main__": remote_host = sys.argv[1] asyncio.run(main(remote_host))