Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
671d4eb
init fanspeed and first mock
Nov 17, 2023
25cc592
working for linux, still wip
Nov 17, 2023
54a5286
add readme and todos
Nov 17, 2023
e87daec
rename venv to be ignored
Nov 18, 2023
3984a55
cleanup
Nov 18, 2023
8836c39
remove debug prints
Nov 18, 2023
618e91e
add Rotation ValueFormat as rpm
Nov 18, 2023
73e8836
rename and create link to Deployments
Nov 18, 2023
5c66f06
change to hardlink, add units, move test to correct location, update …
Nov 18, 2023
0fa394b
addd .vsode
Dec 12, 2023
60c69b6
Merge remote-tracking branch 'origin/main' into dev
Dec 18, 2023
ddec4aa
move network to deploy
Dec 18, 2023
9840359
ignore .code-workspace
Dec 18, 2023
e8d5840
update class organization
Dec 23, 2023
74fd8e8
update methods
Dec 24, 2023
b8e9a50
Merge branch 'main' into network_io
Dec 24, 2023
07fc12d
first mock of config and wifi signal strength update
Dec 26, 2023
6c4fd87
remove vscoe dev dir
Dec 26, 2023
f657509
cleanup workspace
Dec 26, 2023
1ed02c2
restore gitignore to head
Dec 26, 2023
e0803f6
if wireless from iwconfig "interfacename" output
Dec 26, 2023
6ac34ac
first speed calculations
Dec 26, 2023
49544e3
separate Wifi from network entity
Dec 27, 2023
e2746ae
add pattern matching for iwconfig
Dec 27, 2023
2e4dd82
linux wifi implementation working
Jan 2, 2024
1065355
checkSystemSupport for linux
Jan 3, 2024
5baa62f
chcekSystemSupport windows
Jan 3, 2024
18c3e55
add valueformatter, uppdate extra keys, first half of updates
Jan 3, 2024
bfc955f
add todo
Jan 3, 2024
3f9bda7
RunCommand to OsD
infeeeee Jan 3, 2024
ea4e14a
rearrange fix percent and dbm
Jan 4, 2024
815121b
windows implementation feature complete, needs refactoring
Jan 4, 2024
55ad325
remove network fix linux implementation
Jan 4, 2024
5a92382
cleanup
Jan 4, 2024
ff23de5
move logic to checksystemspport
Jan 5, 2024
0caec82
Merge branch 'main' into network_io
Jan 5, 2024
c2372f4
check returncode instead of output
Jan 5, 2024
51abf89
windows fix
Jan 5, 2024
0cf65d4
show only available attributes
Jan 8, 2024
ca4f723
HAss entity properties
richibrics Jan 8, 2024
2805a82
Fix extra attribute and macos: regex, command, language
richibrics Jan 8, 2024
cfcfe7b
macos: use full airport path
richibrics Jan 8, 2024
5985a35
Remove Hass unit
richibrics Jan 8, 2024
a7f363f
change configuration for windows
Jan 9, 2024
67e2e28
change locale to defaultlocale for windows support. fix problems on w…
Jan 9, 2024
7ca3904
Merge branch 'richibrics:main' into network_io
lockenkop Jan 18, 2024
a9ac03e
reduce amount of extraAttributes, cleanup
Jan 22, 2024
4c87860
ctypes boilerplate
Jan 22, 2024
0a64a74
first mock ctypes
Jan 27, 2024
f5d2ecf
iw switch
Jan 27, 2024
3892a8f
macos systemsupport check fix
Jan 27, 2024
1f75c73
Merge branch 'richibrics:main' into network_io
lockenkop Jun 10, 2024
7ba48c3
refactor for linux, remove windows stuff
Jun 11, 2024
177e01f
refactoring for linux and mac support
Jun 13, 2024
d1e7c1e
format extra attributes with their respecting options
Jun 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ IoTuring/Configurator/configurations.json*
IoTuring/Configurator/dontmoveconf.itg*
.venv
build
*.egg-info
*.egg-info
6 changes: 3 additions & 3 deletions IoTuring/Configurator/Configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ def OpenConfigInEditor(self):
editor_command = next(
(e for e in editors if OsD.CommandExists(e)), "")
if editor_command:
subprocess.run(f'{editor_command} "{config_path}"',
shell=True, close_fds=True)
return
OsD.RunCommand(f'{editor_command} "{config_path}"',
shell=True, close_fds=True, capture_output=False)
return

self.Log(self.LOG_WARNING, "No editor found")

Expand Down
270 changes: 270 additions & 0 deletions IoTuring/Entity/Deployments/Wifi/Wifi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import re
from socket import AddressFamily

import psutil
import locale

from IoTuring.Configurator.MenuPreset import MenuPreset
from IoTuring.Entity.Entity import Entity
from IoTuring.Entity.EntityData import EntitySensor
from IoTuring.Entity.ValueFormat import ValueFormatterOptions
from IoTuring.MyApp.SystemConsts import OperatingSystemDetection as OsD

VALUEFORMATTEROPTIONS_DBM = ValueFormatterOptions(ValueFormatterOptions.TYPE_RADIOPOWER)
VALUEFORMATTEROPTIONS_BYTES = ValueFormatterOptions(ValueFormatterOptions.TYPE_BYTE)
VALUEFORMATTEROPTIONS_BIT_PER_SECOND = ValueFormatterOptions(ValueFormatterOptions.TYPE_BIT_PER_SECOND)

WIFI_CHOICE_STRING = "Name: {:<15}, IP: {:<16}, MAC: {:<11}"

CONFIG_KEY_WIFI = "wifi"

SIGNAL_UNIT = "dBm" # windows also supports "%"
SHOW_NA = False # don't show not available extraAttributes

KEY_SIGNAL_STRENGTH_DBM = "signal_strength_dbm"

# LINUX
EXTRA_KEY_NAME = "name"
EXTRA_KEY_DESCRIPTION = "description"
EXTRA_KEY_PHYSICAL_ADDRESS = "physical_address"
EXTRA_KEY_STATE = "state"
EXTRA_KEY_BSSID = "BSSID"
EXTRA_KEY_SSID = "ssid"
EXTRA_KEY_FREQUENCY = "Frequency"
EXTRA_KEY_SIGNAL = "Signal"
EXTRA_KEY_RX_BYTES = "RX_Bytes"
EXTRA_KEY_TX_BYTES = "TX_Bytes"
EXTRA_KEY_RX_BITRATE = "RX_Bitrate"
EXTRA_KEY_TX_BITRATE = "TX_Bitrate"
EXTRA_KEY_BSS_FLAGS = "BSS_Flags"
EXTRA_KEY_DTIM_PERIOD = "DTIM_Period"
EXTRA_KEY_BEACON_INTERVAL = "Beacon_Interval"

# MACOS
EXTRA_KEY_AGRCTLRSSI = "agrCtlRSSI"
EXTRA_KEY_AGREXTRSSI = "agrExtRSSI"
# state already in linux config
EXTRA_KEY_OP_MODE = "OP_mode"
EXTRA_KEY_LASTTXRATE = "Last_TX_Rate"
EXTRA_KEY_MAXRATE = "Max_Rate"
# BSSID already in linux config
# SSID already in linux config
EXTRA_KEY_CHANNEL = "Channel"



class Wifi(Entity):
NAME = "Wifi"
ALLOW_MULTI_INSTANCE = True

def Initialize(self):
self.platform = OsD.GetOs()
self.locale_str, _ = locale.getdefaultlocale()
self.language: str = self.locale_str.split("_")[0]
self.showNA = SHOW_NA

self.wifiInterface = self.GetFromConfigurations(CONFIG_KEY_WIFI)

self.commands = {
OsD.LINUX: ["iw", "dev", self.wifiInterface, "link"],
OsD.MACOS: [
"/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport",
"-I",
],
}
self.patterns = {
OsD.LINUX: {
"BSSID": r'Connected to (\S+) \(on \S+\)',
"SSID": r'SSID: (.+)',
"Frequency": r'freq: ([\d.]+)',
"RX_bytes": r'RX: (\d+) bytes \(\d+ packets\)',
"TX_bytes": r'TX: (\d+) bytes \(\d+ packets\)',
"Signal": r'signal: (-?\d+) dBm',
"RX_bitrate": r'rx bitrate: ([\d.]+) MBit/s',
"TX_bitrate": r'tx bitrate: ([\d.]+) MBit/s',
"BSS_flags": r'bss flags: (.+)',
"DTIM_period": r'dtim period: (\d+)',
"Beacon_interval": r'beacon int: (\d+)'
},
OsD.MACOS: { # no language differentiation in macos: always english
"agrCtlRSSI": r"[^\n][\s]*agrCtlRSSI:\s+(-?\d+)\n",
"agrExtRSSI": r"[^\n][\s]*agrExtRSSI:\s+(-?\d+)\n",
"state": r"[^\n][\s]*state:\s+(\w+)\n",
"op mode": r"[^\n][\s]*op mode:\s+(\w+)\n",
"lastTxRate": r"[^\n][\s]*lastTxRate:\s+(\d+)\n",
"maxRate": r"[^\n][\s]*maxRate:\s+(\d+)\n",
"BSSID": r"[^\n][\s]*BSSID:\s+([\w:]+)\n",
"SSID": r"\n[\s]*SSID:\s+([\w\s]+)\n",
"channel": r"[^\n][\s]*channel:\s+([\d,]+)\n",
},
}

self.keySignalStrength = KEY_SIGNAL_STRENGTH_DBM

self.valueFormatterOptions: dict = {
KEY_SIGNAL_STRENGTH_DBM : VALUEFORMATTEROPTIONS_DBM,
EXTRA_KEY_RX_BITRATE : VALUEFORMATTEROPTIONS_BIT_PER_SECOND,
EXTRA_KEY_RX_BYTES : VALUEFORMATTEROPTIONS_BYTES
}

self.RegisterEntitySensor(
EntitySensor(
self,
key=self.keySignalStrength,
supportsExtraAttributes=True,
valueFormatterOptions=self.valueFormatterOptions[KEY_SIGNAL_STRENGTH_DBM],
),
)

def Update(self):
p = self.RunCommand(self.commands[self.platform])
if not p.stdout:
raise Exception("error in GetWirelessInfo\n", p.stderr)
wifiInfo = self.GetWirelessInfo(p.stdout)
if not wifiInfo:
raise Exception("error while parsing wirelessInfo")
# set signal strength
elif self.platform == OsD.LINUX and "Signal" in wifiInfo:
self.SetEntitySensorValue(
key=self.keySignalStrength, value=wifiInfo["Signal"]
)
elif self.platform == OsD.MACOS and "agrCtlRSSI" in wifiInfo:
self.SetEntitySensorValue(
key=self.keySignalStrength, value=wifiInfo["agrCtlRSSI"]
)
else: # if there is no signal level found the interface might not be connected to an access point
self.SetEntitySensorValue(key=self.keySignalStrength, value="not connected")

# Extra attributes
for key in self.patterns[self.platform]:
extraKey = "EXTRA_KEY_" + key.upper().replace(" ", "_").replace(".", "_")
if key in wifiInfo:
attributevalue = wifiInfo[key]
elif self.showNA:
attributevalue = "not available"
else:
continue

self.SetEntitySensorExtraAttribute(
sensorDataKey=self.keySignalStrength,
attributeKey=globals()[extraKey],
attributeValue=attributevalue,
valueFormatterOptions = self.valueFormatterOptions[extraKey] if extraKey in self.valueFormatterOptions else None
)

def GetWirelessInfo(self, stdout):
wifi_info = {}
for key, pattern in self.patterns[self.platform].items():
match = re.search(pattern, stdout, re.IGNORECASE)
if match:
wifi_info[key] = match.group(1) if match.group(1) else match.group(0)
return wifi_info

@classmethod
def ConfigurationPreset(cls) -> MenuPreset:
NIC_CHOICES = Wifi.GetWifiNics(getInfo=True)

preset = MenuPreset()
preset.AddEntry(
name="Interface to check",
key=CONFIG_KEY_WIFI,
mandatory=True,
question_type="select",
choices=NIC_CHOICES,
)
return preset

@staticmethod
def GetWifiNics(getInfo=True):
interfaces = psutil.net_if_addrs()
NIC_CHOICES = []

def appendNicChoice(interfaceName, nicip4="", nicip6="", nicmac=""):
NIC_CHOICES.append(
{
"name": WIFI_CHOICE_STRING.format(
interfaceName,
nicip4 if nicip4 else nicip6, # defaults to showing ipv4
nicmac,
),
"value": interfaceName,
}
)

ip4 = ""
ip6 = ""
mac = ""

if OsD.IsLinux():
for interface in interfaces:
p = OsD.RunCommand(["iw", "dev", interface, "link"])
if (
p.returncode > 0
): # if the returncode is 0 iwconfig succeeded, else continue with next interface
continue
if not getInfo:
appendNicChoice(interface)
continue
else:
nicinfo = interfaces[interface] # TODO Typehint
for nicaddr in nicinfo:
if nicaddr.family == AddressFamily.AF_INET:
ip4 = nicaddr.address
continue
elif nicaddr.family == AddressFamily.AF_INET6:
ip6 = nicaddr.address
continue
elif nicaddr.family == psutil.AF_LINK:
mac = nicaddr.address
continue
appendNicChoice(interface, ip4, ip6, mac)
return NIC_CHOICES

elif OsD.IsMacos():
for interface in interfaces:
p = OsD.RunCommand(["airport", interface])
if (
p.returncode > 0
): # if the returncode is 0 iwconfig succeeded, else continue with next interface
continue
nicinfo = interfaces[interface] # TODO Typehint
if not getInfo:
appendNicChoice(interface)
continue
else:
for nicaddr in nicinfo:
if nicaddr.family == AddressFamily.AF_INET:
ip4 = nicaddr.address
continue
elif nicaddr.family == AddressFamily.AF_INET6:
ip6 = nicaddr.address
continue
elif nicaddr.family == psutil.AF_LINK:
mac = nicaddr.address
continue

appendNicChoice(interface, ip4, ip6, mac)
return NIC_CHOICES

@classmethod
def CheckSystemSupport(cls):
if OsD.IsLinux():
if not OsD.CommandExists("iw"):
raise Exception("iw not found")
wifiNics = Wifi.GetWifiNics(getInfo=False)
if not wifiNics:
raise Exception("no wireless interface found")

elif OsD.IsWindows():
# Windows support is WIP https://github.com/richibrics/IoTuring/pull/89
raise Exception("Windows is not supported at the moment")

elif OsD.IsMacos():
if not OsD.CommandExists("/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport"):
raise Exception("airport not found")
wifiNics = Wifi.GetWifiNics(getInfo=False)
if not wifiNics:
raise Exception("no wireless interface found")

else:
raise Exception("OS detection failed")
20 changes: 3 additions & 17 deletions IoTuring/Entity/Entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,29 +187,14 @@ def RunCommand(self,
subprocess.CompletedProcess: See subprocess docs
"""

# different defaults than in subprocess:
defaults = {
"capture_output": True,
"text": True
}

for param, value in defaults.items():
if param not in kwargs:
kwargs[param] = value

try:
if shell == False and isinstance(command, str):
runcommand = command.split()
else:
runcommand = command

if command_name:
command_name = self.NAME + "-" + command_name
else:
command_name = self.NAME

p = subprocess.run(
runcommand, shell=shell, **kwargs)
p = OsD.RunCommand(command, shell=shell, **kwargs)

self.Log(self.LOG_DEBUG, f"Called {command_name} command: {p}")

Expand All @@ -218,11 +203,12 @@ def RunCommand(self,
if p.stderr:
self.Log(error_loglevel,
f"Error during {command_name} command: {p.stderr}")

return p

except Exception as e:
raise Exception(f"Error during {command_name} command: {str(e)}")

return p

@classmethod
def CheckSystemSupport(cls):
Expand Down
Loading