Skip to content

Commit

Permalink
Add polkadot parachain + get states refactoring (#79)
Browse files Browse the repository at this point in the history
* get states -> telemetry class

* add file not found error on setup

* choose network

* default network polkadot + update version
  • Loading branch information
LoSk-p authored Oct 28, 2024
1 parent b34d76c commit c281219
Show file tree
Hide file tree
Showing 18 changed files with 641 additions and 465 deletions.
24 changes: 18 additions & 6 deletions custom_components/robonomics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import asyncio
import logging
import os
import json
import shutil
from datetime import timedelta

Expand Down Expand Up @@ -60,8 +61,9 @@
TIME_CHANGE_LIBP2P_UNSUB,
CONTROLLER_ADDRESS,
CONF_CONTROLLER_TYPE,
TELEMETRY_SENDER,
CONF_NETWORK,
)
from .get_states import get_and_send_data, get_states_libp2p
from .ipfs import (
create_folders,
wait_ipfs_daemon,
Expand All @@ -76,6 +78,8 @@
save_video,
)
from .libp2p import LibP2P
from .telemetry_helpers import Telemetry
from .hass_helpers import HassStatesHelper


async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
Expand Down Expand Up @@ -107,6 +111,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
else:
hass.data[DOMAIN][PINATA] = None
_LOGGER.debug("Use local node to pin files")
hass.data[DOMAIN][TELEMETRY_SENDER].setup(hass.data[DOMAIN][CONF_SENDING_TIMEOUT])
hass.data[DOMAIN][TIME_CHANGE_UNSUB]()
hass.data[DOMAIN][TIME_CHANGE_UNSUB] = async_track_time_interval(
hass,
Expand Down Expand Up @@ -150,7 +155,8 @@ async def init_integration(_: Event = None) -> None:
if DOMAIN not in hass.data:
return
try:
msg = await get_states_libp2p(hass)
states = await HassStatesHelper(hass).get_states(with_history=False)
msg = hass.data[DOMAIN][ROBONOMICS].encrypt_for_devices(json.dumps(states))
await hass.data[DOMAIN][LIBP2P].send_states_to_websocket(msg)
except Exception as e:
_LOGGER.error(f"Exception in first send libp2p states {e}")
Expand All @@ -162,8 +168,9 @@ async def init_integration(_: Event = None) -> None:
)
except Exception as e:
_LOGGER.error(f"Exception in first check devices {e}")
await hass.data[DOMAIN][TELEMETRY_SENDER].send()

await get_and_send_data(hass)
# await get_and_send_data(hass)

_LOGGER.debug("Robonomics user control starting set up")
conf = entry.data
Expand All @@ -186,8 +193,11 @@ async def init_integration(_: Event = None) -> None:
hass,
hass.data[DOMAIN][CONF_SUB_OWNER_ADDRESS],
hass.data[DOMAIN][CONF_ADMIN_SEED],
conf.get(CONF_CONTROLLER_TYPE)
conf.get(CONF_CONTROLLER_TYPE),
conf.get(CONF_NETWORK)
)
hass.data[DOMAIN][TELEMETRY_SENDER] = Telemetry(hass)
hass.data[DOMAIN][TELEMETRY_SENDER].setup(hass.data[DOMAIN][CONF_SENDING_TIMEOUT])
controller_account = hass.data[DOMAIN][ROBONOMICS].controller_account

hass.data[DOMAIN][CONTROLLER_ADDRESS] = hass.data[DOMAIN][
Expand Down Expand Up @@ -252,7 +262,7 @@ async def handle_time_changed(event):
hass.data[DOMAIN][TIME_CHANGE_COUNT] = 0
await hass.data[DOMAIN][ROBONOMICS].check_subscription_left_days()
_LOGGER.debug(f"Time changed: {event}")
await get_and_send_data(hass)
# await get_and_send_data(hass)
except Exception as e:
_LOGGER.error(f"Exception in handle_time_changed: {e}")

Expand All @@ -273,7 +283,8 @@ async def add_libp2p_states_to_queue(old_state, new_state):
It calls every timeout from config to get and send telemtry.
"""
try:
msg = await get_states_libp2p(hass)
states = await HassStatesHelper(hass).get_states(with_history=False)
msg = hass.data[DOMAIN][ROBONOMICS].encrypt_for_devices(json.dumps(states))
async with lock:
if len(libp2p_message_queue) == 0:
libp2p_message_queue.append(msg)
Expand Down Expand Up @@ -401,6 +412,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
:return: True when integration is unloaded
"""

hass.data[DOMAIN][TELEMETRY_SENDER].unload()
hass.data[DOMAIN][TIME_CHANGE_UNSUB]()
hass.data[DOMAIN][TIME_CHANGE_LIBP2P_UNSUB]()
await hass.data[DOMAIN][LIBP2P].close_connection()
Expand Down
114 changes: 82 additions & 32 deletions custom_components/robonomics/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,16 @@
from robonomicsinterface import RWS, Account
from substrateinterface import KeypairType, Keypair
from substrateinterface.utils.ss58 import is_valid_ss58_address
from homeassistant.helpers.selector import FileSelector, FileSelectorConfig, TextSelector, TextSelectorConfig, TextSelectorType
from homeassistant.helpers.selector import (
FileSelector,
FileSelectorConfig,
TextSelector,
TextSelectorConfig,
TextSelectorType,
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
)
from homeassistant.components.file_upload import process_uploaded_file

from .const import (
Expand All @@ -37,6 +46,11 @@
CONF_PASSWORD,
CONF_CONFIG_FILE,
CONF_CONTROLLER_TYPE,
CONF_NETWORK,
CONF_KUSAMA,
CONF_POLKADOT,
ROBONOMICS_WSS_POLKADOT,
ROBONOMICS_WSS_KUSAMA,
DOMAIN,
)
from .exceptions import (
Expand All @@ -54,9 +68,23 @@

STEP_USER_DATA_SCHEMA_FIELDS = {}
PASSWORD_SELECTOR = TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWORD))
STEP_USER_DATA_SCHEMA_FIELDS[CONF_CONFIG_FILE] = FileSelector(FileSelectorConfig(accept=".json,application/json"))
STEP_USER_DATA_SCHEMA_FIELDS[CONF_PASSWORD] = PASSWORD_SELECTOR
STEP_USER_DATA_SCHEMA = vol.Schema(STEP_USER_DATA_SCHEMA_FIELDS)
NETWORK_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=[CONF_KUSAMA, CONF_POLKADOT],
mode=SelectSelectorMode.DROPDOWN,
translation_key="network",
)
)

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_CONFIG_FILE): FileSelector(
FileSelectorConfig(accept=".json,application/json")
),
vol.Required(CONF_PASSWORD): PASSWORD_SELECTOR,
vol.Required(CONF_NETWORK, default=CONF_POLKADOT): NETWORK_SELECTOR,
}
)

STEP_WARN_DATA_SCHEMA = vol.Schema(
{
Expand All @@ -65,6 +93,11 @@
}
)

def get_network_ws(network_key: str) -> str:
if network_key == CONF_KUSAMA:
return ROBONOMICS_WSS_KUSAMA[0]
elif network_key == CONF_POLKADOT:
return ROBONOMICS_WSS_POLKADOT[0]

@to_thread
def _is_ipfs_local_connected() -> bool:
Expand All @@ -80,23 +113,27 @@ def _is_ipfs_local_connected() -> bool:
return False


async def _has_sub_owner_subscription(hass: HomeAssistant, sub_owner_address: str) -> bool:
async def _has_sub_owner_subscription(
hass: HomeAssistant, sub_owner_address: str, network: str
) -> bool:
"""Check if controller account is in subscription devices
:param sub_owner_address: Subscription owner address
:return: True if ledger is not None, false otherwise
"""

rws = RWS(Account())
rws = RWS(Account(remote_ws = get_network_ws(network)))
res = await hass.async_add_executor_job(rws.get_ledger, sub_owner_address)
if res is None:
return False
else:
return True


async def _is_sub_admin_in_subscription(hass: HomeAssistant, controller_seed: str, sub_owner_address: str) -> bool:
async def _is_sub_admin_in_subscription(
hass: HomeAssistant, controller_seed: str, sub_owner_address: str, network: str
) -> bool:
"""Check if controller account is in subscription devices
:param sub_admin_seed: Controller's seed
Expand All @@ -105,7 +142,7 @@ async def _is_sub_admin_in_subscription(hass: HomeAssistant, controller_seed: st
:return: True if controller account is in subscription devices, false otherwise
"""

rws = RWS(Account(controller_seed, crypto_type=KeypairType.ED25519))
rws = RWS(Account(controller_seed, crypto_type=KeypairType.ED25519, remote_ws = get_network_ws(network)))
res = await hass.async_add_executor_job(rws.is_in_sub, sub_owner_address)
return res

Expand Down Expand Up @@ -148,10 +185,12 @@ async def _validate_config(hass: HomeAssistant, data: dict[str, Any]) -> dict[st
raise InvalidSubAdminSeed
if not _is_valid_sub_owner_address(data[CONF_SUB_OWNER_ADDRESS]):
raise InvalidSubOwnerAddress
if not await _has_sub_owner_subscription(hass, data[CONF_SUB_OWNER_ADDRESS]):
if not await _has_sub_owner_subscription(
hass, data[CONF_SUB_OWNER_ADDRESS], data[CONF_NETWORK]
):
raise NoSubscription
if not await _is_sub_admin_in_subscription(
hass, data[CONF_ADMIN_SEED], data[CONF_SUB_OWNER_ADDRESS]
hass, data[CONF_ADMIN_SEED], data[CONF_SUB_OWNER_ADDRESS], data[CONF_NETWORK]
):
raise ControllerNotInDevices
if not await _is_ipfs_local_connected():
Expand Down Expand Up @@ -216,28 +255,34 @@ async def async_step_conf(
step_id="conf", data_schema=STEP_USER_DATA_SCHEMA
)
_LOGGER.debug(f"User data: {user_input}")
config = self._parse_config_file(user_input[CONF_CONFIG_FILE], user_input[CONF_PASSWORD])

errors = {}
try:
info = await _validate_config(self.hass, config)
except InvalidSubAdminSeed:
errors["base"] = "invalid_sub_admin_seed"
except InvalidSubOwnerAddress:
errors["base"] = "invalid_sub_owner_address"
except NoSubscription:
errors["base"] = "has_no_subscription"
except ControllerNotInDevices:
errors["base"] = "is_not_in_devices"
except CantConnectToIPFS:
errors["base"] = "can_connect_to_ipfs"
except InvalidConfigPassword:
errors["base"] = "wrong_password"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
if CONF_CONFIG_FILE in user_input:
config = self._parse_config_file(
user_input[CONF_CONFIG_FILE], user_input[CONF_PASSWORD]
)
config[CONF_NETWORK] = user_input[CONF_NETWORK]

try:
info = await _validate_config(self.hass, config)
except InvalidSubAdminSeed:
errors["base"] = "invalid_sub_admin_seed"
except InvalidSubOwnerAddress:
errors["base"] = "invalid_sub_owner_address"
except NoSubscription:
errors["base"] = "has_no_subscription"
except ControllerNotInDevices:
errors["base"] = "is_not_in_devices"
except CantConnectToIPFS:
errors["base"] = "can_connect_to_ipfs"
except InvalidConfigPassword:
errors["base"] = "wrong_password"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=info["title"], data=config)
else:
return self.async_create_entry(title=info["title"], data=config)
errors["base"] = "file_not_found"

return self.async_show_form(
step_id="conf", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
Expand All @@ -249,14 +294,18 @@ def _parse_config_file(self, config_file_id: str, password: str) -> dict:
config_file_data = json.loads(config_file_data)
config = {}
try:
controller_kp = Keypair.create_from_encrypted_json(json.loads(config_file_data.get("controllerkey")), password)
controller_kp = Keypair.create_from_encrypted_json(
json.loads(config_file_data.get("controllerkey")), password
)
config[CONF_ADMIN_SEED] = f"0x{controller_kp.private_key.hex()}"
config[CONF_CONTROLLER_TYPE] = controller_kp.crypto_type
except CryptoError:
config[CONF_ADMIN_SEED] = None
config[CONF_CONTROLLER_TYPE] = None
config[CONF_SUB_OWNER_ADDRESS] = config_file_data.get("owner")
if config_file_data.get("pinatapublic") and config_file_data.get("pinataprivate"):
if config_file_data.get("pinatapublic") and config_file_data.get(
"pinataprivate"
):
config[CONF_PINATA_PUB] = config_file_data.get("pinatapublic")
config[CONF_PINATA_SECRET] = config_file_data.get("pinataprivate")
if config_file_data.get("ipfsurl"):
Expand All @@ -267,6 +316,7 @@ def _parse_config_file(self, config_file_id: str, password: str) -> dict:
_LOGGER.debug(f"Config: {config}")
return config


class OptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialise options flow. THis class contains methods to manage config after it was initialised."""
Expand Down
8 changes: 7 additions & 1 deletion custom_components/robonomics/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
CONF_PASSWORD = "password"
CONF_CONFIG_FILE = "config_file"
CONF_CONTROLLER_TYPE = "controller_type"
CONF_NETWORK = "parachain_network"
CONF_KUSAMA = "kusama"
CONF_POLKADOT = "polkadot"

CONTROLLER_ADDRESS = "controller_address"

Expand All @@ -34,13 +37,16 @@
CRUST_GATEWAY_1 = "https://ipfs.living/ipfs/"
CRUST_GATEWAY_2 = "https://crustipfs.live/ipfs/"

ROBONOMICS_WSS = [
ROBONOMICS_WSS_KUSAMA = [
"wss://kusama.rpc.robonomics.network/",
"wss://robonomics.0xsamsara.com/",
]

ROBONOMICS_WSS_POLKADOT = ["wss://polkadot.rpc.robonomics.network/"]

SENDING_TIMEOUT = "sending_timeout"
ROBONOMICS = "robonomics"
TELEMETRY_SENDER = "telemetry_sender"
PINATA = "pinata"
IPFS_API = "ipfs_api"
HANDLE_TIME_CHANGE = "hadle_time_change"
Expand Down
Loading

0 comments on commit c281219

Please sign in to comment.