Wi-Fi Hotspot UI

This commit is contained in:
Robin Davies
2024-09-12 19:12:07 -04:00
parent 66e46b8d39
commit f34ca1f0da
22 changed files with 1155 additions and 530 deletions
+7 -6
View File
@@ -19,14 +19,15 @@ add_subdirectory("react")
add_subdirectory("src")
add_subdirectory("NetworkManagerP2P")
# Replaced with hotspot.
#add_subdirectory("NetworkManagerP2P")
install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
)
# install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
# )
install (TARGETS pipedal_nm_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
)
# install (TARGETS pipedal_nm_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
# )
# select LV2 source directory for the current build architecture
@@ -99,7 +100,7 @@ set(CPACK_DEBIAN_PACKAGE_SECTION sound)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
#set(CPACK_DEBIAN_PACKAGE_DEPENDS "jackd2, hostapd, dhcpcd, dnsmasq" )
set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, hostapd, dhcpcd,dnsmasq, authbind, gpg" )
set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, hostapd, authbind, gpg" )
#set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64")
set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
+10 -12
View File
@@ -116,12 +116,6 @@ public:
static std::vector<std::unique_ptr<IDBusLogger>> loggers;
void SetDBusLogger(std::unique_ptr<IDBusLogger> && logger)
{
loggers.clear();
loggers.push_back(std::move(logger));
}
void SetDBusLogLevel(DBusLogLevel level)
{
@@ -231,11 +225,17 @@ void LogTrace(const std::string&path,const char*method,const std::string&message
}
}
void SetDBusLogger(std::unique_ptr<IDBusLogger> && logger)
{
loggers.clear();
loggers.push_back(std::move(logger));
}
void SetDBusConsoleLogger()
{
loggers.clear();
loggers.push_back(std::make_unique<ConsoleDBusLogger>());
SetDBusLogger(std::make_unique<ConsoleDBusLogger>());
}
void AddDBusConsoleLogger()
{
@@ -243,13 +243,11 @@ void AddDBusConsoleLogger()
}
void SetDBusSystemdLogger()
{
loggers.clear();
loggers.push_back(std::make_unique<SystemdDBusLogger>());
SetDBusLogger(std::make_unique<SystemdDBusLogger>());
}
void SetDBusFileLogger(const std::filesystem::path &path)
{
loggers.clear();
loggers.push_back(std::make_unique<FileDBusLogger>(path));
SetDBusLogger(std::make_unique<FileDBusLogger>(path));
}
+117 -45
View File
@@ -102,6 +102,8 @@ void WifiConfigSettings::Load()
reader.read(this);
}
this->countryCode_ = getWifiCountryCode();
this->enable_ = this->autoStartMode_ != (uint16_t)HotspotAutoStartMode::Never;
this->mdnsName_ = this->hotspotName_;
}
catch (const std::exception &e)
{
@@ -143,12 +145,27 @@ static void openWithPerms(
void WifiConfigSettings::Save()
{
WifiConfigSettings newSettings {*this};
// sync legacy settings, just in case i don't know what.
newSettings.mdnsName_ = newSettings.hotspotName_;
newSettings.enable_ = newSettings.IsEnabled();
// fill in the password, if required.
if (!newSettings.hasPassword_)
{
WifiConfigSettings oldSettings;
oldSettings.Load();
newSettings.hasPassword_ = oldSettings.hasPassword_;
newSettings.password_ = oldSettings.password_;
}
newSettings.hasSavedPassword_ = newSettings.hasPassword_;
try
{
ofstream_synced f;
openWithPerms(f,CONFIG_PATH);
json_writer writer(f);
writer.write(this);
writer.write(&newSettings);
}
catch (const std::exception &e)
{
@@ -398,6 +415,7 @@ namespace pipedal::priv
JSON_MAP_BEGIN(WifiConfigSettings)
// v0
JSON_MAP_REFERENCE(WifiConfigSettings, valid)
JSON_MAP_REFERENCE(WifiConfigSettings, wifiWarningGiven)
JSON_MAP_REFERENCE(WifiConfigSettings, rebootRequired)
@@ -408,8 +426,13 @@ JSON_MAP_REFERENCE(WifiConfigSettings, hasPassword)
JSON_MAP_REFERENCE(WifiConfigSettings, password)
JSON_MAP_REFERENCE(WifiConfigSettings, countryCode)
JSON_MAP_REFERENCE(WifiConfigSettings, channel)
JSON_MAP_REFERENCE(WifiConfigSettings, alwaysOn)
JSON_MAP_REFERENCE(WifiConfigSettings, homeNetworks)
// v1: Auto-hotspot
JSON_MAP_REFERENCE(WifiConfigSettings, autoStartMode)
JSON_MAP_REFERENCE(WifiConfigSettings, homeNetwork)
JSON_MAP_REFERENCE(WifiConfigSettings, hasSavedPassword)
JSON_MAP_END()
int32_t pipedal::ChannelToChannelNumber(const std::string &channel)
@@ -646,22 +669,33 @@ static std::vector<WifiConfigSettings::ssid_t> stringToSsidArray(const std::stri
return result;
}
void WifiConfigSettings::ParseArguments(const std::vector<std::string> &argv)
void WifiConfigSettings::ParseArguments(
const std::vector<std::string> &argv,
HotspotAutoStartMode startMode,
const std::string homeNetworkSsid
)
{
this->valid_ = false;
if (argv.size() < 4 || argv.size() > 6)
if (argv.size() != 4)
{
throw invalid_argument("Invalid number of arguments.");
}
this->enable_ = true;
WifiConfigSettings oldSettings;
oldSettings.Load();
this->valid_ = false;
this->autoStartMode_ = (int16_t)startMode;
this->enable_ = startMode != HotspotAutoStartMode::Never;
this->homeNetwork_ = homeNetworkSsid;
this->countryCode_ = argv[0];
this->hotspotName_ = argv[1];
this->mdnsName_ = this->hotspotName_;
this->password_ = argv[2];
this->channel_ = argv[3];
this->hasPassword_ = this->password_.length() != 0;
this->homeNetworks_ = std::vector<ssid_t>();
this->alwaysOn_ = false;
this->hasSavedPassword_ = oldSettings.hasSavedPassword_;
if (!ValidateCountryCode(this->countryCode_))
@@ -672,26 +706,20 @@ void WifiConfigSettings::ParseArguments(const std::vector<std::string> &argv)
throw invalid_argument("Hotspot name is too long.");
if (this->hotspotName_.length() < 1)
throw invalid_argument("Hotspot name is too short.");
if (this->password_.size() != 0 && this->password_.size() < 8)
throw invalid_argument("Passphrase must be at least 8 characters long.");
if (this->password_.size() == 0 && !this->hasSavedPassword_)
{
throw invalid_argument("Passphrase required.");
}
if (!ValidateChannel(this->countryCode_, this->channel_))
{
throw invalid_argument("Channel is not valid.");
}
if (argv.size() >= 5)
{
if (!TryStringToBool(argv[4],&this->alwaysOn_))
{
throw std::runtime_error(SS("Invalid boolean value: " << argv[4]));
}
}
if (argv.size() >= 6)
{
this->homeNetworks_ = stringToSsidArray(argv[5]);
}
// validate that the channel number is supported for the given country code.
this->valid_ = true;
@@ -701,15 +729,17 @@ bool WifiConfigSettings::ConfigurationChanged(const WifiConfigSettings&other) co
{
return !(
this->valid_ == other.valid_ &&
this->rebootRequired_ == other.rebootRequired_ &&
this->enable_ == other.enable_ &&
//this->rebootRequired_ == other.rebootRequired_ &&
//this->enable_ == other.enable_ &&
this->countryCode_ == other.countryCode_ &&
this->mdnsName_ == other.mdnsName_ &&
this->hotspotName_ == other.hotspotName_ &&
this->hasPassword_ == other.hasPassword_ &&
this->password_ == other.password_ &&
this->channel_ == other.channel_ &&
this->homeNetworks_ == other.homeNetworks_ &&
this->alwaysOn_ == other.alwaysOn_
this->homeNetwork_ == other.homeNetwork_ &&
this->autoStartMode_ == other.autoStartMode_ &&
this->hasSavedPassword_ == other.hasSavedPassword_
);
}
@@ -718,33 +748,75 @@ bool WifiConfigSettings::operator==(const WifiConfigSettings&other) const
return !ConfigurationChanged(other) && this->wifiWarningGiven_ == other.wifiWarningGiven_;
}
bool WifiConfigSettings::WantsHotspot(bool ethernetConnected, const std::vector<ssid_t> &availableNetworks)
static bool CanSeeHomeNetwork(const std::string&home, const std::vector<std::string>&availableNetworks)
{
if ((!this->valid_) || (!this->enable_))
return false;
if (ethernetConnected)
for (const auto &availableNetwork: availableNetworks)
{
return false;
}
if (!homeNetworks_.empty())
{
for (auto &network: availableNetworks)
if (availableNetwork == home)
{
for (auto&homeNetwork: homeNetworks_)
{
if (network == homeNetwork)
{
return false;
}
}
return true;
}
}
if (alwaysOn_)
return false;
}
bool WifiConfigSettings::WantsHotspot(
bool ethernetConnected,
const std::vector<std::string> &availableRememberedNetworks,
const std::vector<std::string> &availableNetworks)
{
if ((!this->valid_))
return false;
HotspotAutoStartMode autoStartMode = (HotspotAutoStartMode)this->autoStartMode_;
switch (autoStartMode)
{
return true;
case HotspotAutoStartMode::Never:
default:
return false;
case HotspotAutoStartMode::NoEthernetConnection:
return !ethernetConnected;
case HotspotAutoStartMode::NotAtHome:
return !CanSeeHomeNetwork(this->homeNetwork_, availableNetworks);
case HotspotAutoStartMode::NoRememberedWifiConections:
return availableRememberedNetworks.size() == 0;
case HotspotAutoStartMode::Always:
return true;
}
return availableNetworks.size() == 0;
}
std::string pipedal::ssidToString(const std::vector<uint8_t> &ssid)
{
std::stringstream s;
for (auto v: ssid)
{
if (v == 0) break; // breaks for some arguably legal ssids, but I can live with that.
s << (char)v;
}
return s.str();
}
std::vector<std::string> pipedal::ssidToStringVector(const std::vector<std::vector<uint8_t>> &ssids)
{
std::vector<std::string> result;
result.reserve(ssids.size());
for (const std::vector<uint8_t> &ssid: ssids)
{
result.push_back(ssidToString(ssid));
}
return result;
}
bool WifiConfigSettings::WantsHotspot(
bool ethernetConnected,
const std::vector<std::vector<uint8_t>> &availableRememberedNetworks, // remembered networks that are currently visible
const std::vector<std::vector<uint8_t>> &availableNetworks // all visible networks.
)
{
std::vector<std::string> sAvailableRememberedNetworks = ssidToStringVector(availableRememberedNetworks);
std::vector<std::string> sAvailableNetworks = ssidToStringVector(availableNetworks);
return WantsHotspot(ethernetConnected,sAvailableRememberedNetworks,sAvailableNetworks);
}
@@ -20,6 +20,8 @@
#pragma once
#include "json.hpp"
#include <vector>
#include <string>
#ifndef NEW_WIFI_CONFIG
@@ -39,12 +41,23 @@
#endif
namespace pipedal {
std::string ssidToString(const std::vector<uint8_t> &ssid);
std::vector<std::string> ssidToStringVector(const std::vector<std::vector<uint8_t>> &ssids);
uint32_t ChannelToWifiFrequency(const std::string &channel);
uint32_t ChannelToWifiFrequency(uint32_t channel);
int32_t ChannelToChannelNumber(const std::string&channel);
enum class HotspotAutoStartMode {
// saved to file. Do not remove or renumber enums.
Never = 0,
NoEthernetConnection = 1,
NotAtHome = 2,
NoRememberedWifiConections = 3,
Always = 4
};
class WifiConfigSettings {
public:
using ssid_t = std::vector<uint8_t>;
@@ -53,26 +66,45 @@ namespace pipedal {
void Load();
void Save();
bool valid_ = false;
bool wifiWarningGiven_ = false;
bool rebootRequired_ = false;
bool enable_ = false;
int16_t autoStartMode_ = 0; // see HotspotAutoStartMode
bool hasSavedPassword_ = false;
std::string homeNetwork_;
std::string countryCode_ = "US"; // iso 3661
std::string hotspotName_ = "pipedal";
std::string mdnsName_ = "pipedal";
std::vector<ssid_t> homeNetworks_;
bool hasPassword_ = false;
std::string password_;
std::string channel_ = "";
bool alwaysOn_ = false;
void ParseArguments(const std::vector<std::string> &arguments);
bool wifiWarningGiven_ = false; // Do not use. Present only for backward compatibility.
bool valid_ = false; // Do not use. Present only for backward compatibility.
private:
bool rebootRequired_ = false; // Do not use. Present only for backward compatibility.
std::string mdnsName_ = "pipedal";
bool enable_ = false; // Do not use. Present only for backward compatibility.
public:
bool IsEnabled() const { return autoStartMode_ != 0; }
// Initialize from commandline arguments (see ConfigMain.cpp)
void ParseArguments(
const std::vector<std::string> &argv,
HotspotAutoStartMode startMode,
const std::string homeNetworkSsid
);
static bool ValidateCountryCode(const std::string&value);
static bool ValidateChannel(const std::string&countryCode,const std::string&value);
bool operator==(const WifiConfigSettings&other) const;
bool ConfigurationChanged(const WifiConfigSettings&other) const;
bool WantsHotspot(bool ethernetConnected,const std::vector<ssid_t> &availableNetworks);
bool WantsHotspot(
bool ethernetConnected,
const std::vector<std::string> &availableRememberedNetworks, // remembered networks that are currently visible
const std::vector<std::string> &availableNetworks // all visible networks.
);
bool WantsHotspot(
bool ethernetConnected,
const std::vector<std::vector<uint8_t>> &availableRememberedNetworks, // remembered networks that are currently visible
const std::vector<std::vector<uint8_t>> &availableNetworks // all visible networks.
);
public:
DECLARE_JSON_MAP(WifiConfigSettings);
+24
View File
@@ -335,6 +335,14 @@ namespace pipedal
{
os << value;
}
void write(short value)
{
os << value;
}
void write(unsigned short value)
{
os << value;
}
void write(long long value)
{
os << value;
@@ -897,6 +905,22 @@ namespace pipedal
if (is_.fail())
throw JsonException("Invalid format.");
}
void read(short * value)
{
skip_whitespace();
is_ >> *value;
if (is_.fail())
throw JsonException("Invalid format.");
}
void read(unsigned short * value)
{
skip_whitespace();
is_ >> *value;
if (is_.fail())
throw JsonException("Invalid format.");
}
void read(int *value)
{
skip_whitespace();
+39 -14
View File
@@ -366,7 +366,8 @@ export class PiPedalModel //implements PiPedalModel
clientId: number = -1;
serverVersion?: PiPedalVersion;
countryCodes: Object = {};
countryCodes: {[Name: string]: string} = {};
socketServerUrl: string = "";
varServerUrl: string = "";
lv2Path: string = "";
@@ -956,7 +957,7 @@ export class PiPedalModel //implements PiPedalModel
return response.json();
})
.then((countryCodes) => {
this.countryCodes = countryCodes as Object;
this.countryCodes = countryCodes as {[Name: string]: string};
return this.getWebSocket().request<number>("hello");
})
@@ -2556,27 +2557,33 @@ export class PiPedalModel //implements PiPedalModel
let oldSettings = this.wifiConfigSettings.get();
wifiConfigSettings = wifiConfigSettings.clone();
if ((!oldSettings.enable) && (!wifiConfigSettings.enable)) {
if ((!oldSettings.isEnabled()) && (!wifiConfigSettings.isEnabled())) {
// no effective change.
resolve();
return;
}
if (!wifiConfigSettings.enable) {
if (!wifiConfigSettings.isEnabled()) {
wifiConfigSettings.hasPassword = false;
wifiConfigSettings.hotspotName = oldSettings.hotspotName;
wifiConfigSettings.password = "";
} else {
if (wifiConfigSettings.countryCode === oldSettings.countryCode
&& wifiConfigSettings.channel === oldSettings.channel
&& wifiConfigSettings.hotspotName === oldSettings.hotspotName) {
if (!wifiConfigSettings.hasPassword) {
// no effective change.
resolve();
return;
if (wifiConfigSettings.hasPassword)
{
wifiConfigSettings.hasSavedPassword = true;
}
}
}
// save a version for the server (potentially carrying a password)
let serverConfigSettings = wifiConfigSettings.clone();
let serverConfigSettings: WifiConfigSettings;
if (wifiConfigSettings.isEnabled()) {
serverConfigSettings = wifiConfigSettings.clone();
} else {
// avoid leaking edits to the server
serverConfigSettings = oldSettings.clone();
serverConfigSettings.autoStartMode = wifiConfigSettings.autoStartMode;
wifiConfigSettings = oldSettings.clone();
wifiConfigSettings.autoStartMode = 0;
}
wifiConfigSettings.hasPassword = false;
wifiConfigSettings.password = "";
this.wifiConfigSettings.set(wifiConfigSettings);
@@ -2661,6 +2668,24 @@ export class PiPedalModel //implements PiPedalModel
return result;
}
getKnownWifiNetworks() : Promise<string[]> {
let result = new Promise<string[]>((resolve, reject) => {
if (!this.webSocket) {
reject("Connection closed.");
return;
}
this.webSocket.request<string[]>("getKnownWifiNetworks")
.then((data) => {
resolve(data);
})
.catch((err) => reject(err));
});
return result;
}
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]> {
let result = new Promise<WifiChannel[]>((resolve, reject) => {
if (!this.webSocket) {
+14 -27
View File
@@ -681,6 +681,20 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<div >
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">CONNECTION</Typography>
<ButtonBase
className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
onClick={() => this.handleShowWifiConfigDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Wi-Fi auto hotspot</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.wifiConfigSettings.getSummaryText()}
</Typography>
</div>
</ButtonBase>
{
this.state.isAndroidHosted &&
(
@@ -699,33 +713,6 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
)
}
<ButtonBase
className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
onClick={() => this.handleShowWifiConfigDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Wi-Fi auto hotspot</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.wifiConfigSettings.getSummaryText()}
</Typography>
</div>
</ButtonBase>
<ButtonBase className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
style={{ display: "none" }}
onClick={() => this.handleShowWifiConfigDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Configure Wi-Fi hotspot</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.wifiConfigSettings.getSummaryText()}
</Typography>
</div>
</ButtonBase>
</div>
{(!this.props.onboarding) ? (
<div >
+555 -269
View File
@@ -18,17 +18,19 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React from 'react';
import IconButton from '@mui/material/IconButton';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import FormHelperText from '@mui/material/FormHelperText';
import Button from '@mui/material/Button';
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import DialogEx from './DialogEx';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Switch from '@mui/material/Switch';
import FormControlLabel from '@mui/material/FormControlLabel';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import WifiConfigSettings from './WifiConfigSettings';
import NoChangePassword from './NoChangePassword';
import Typography from '@mui/material/Typography';
import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles';
@@ -39,7 +41,8 @@ import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import WifiChannel from './WifiChannel';
import {PiPedalModel, PiPedalModelFactory} from './PiPedalModel';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
const styles = (theme: Theme) => createStyles({
pgraph: {
@@ -57,319 +60,602 @@ export interface WifiConfigProps extends WithStyles<typeof styles> {
}
export interface WifiConfigState {
autoStartMode: number;
showPassword: boolean;
fullScreen: boolean;
compactWidth: boolean;
compactHeight: boolean;
showWifiWarningDialog: boolean;
showHelpDialog: boolean;
wifiWarningGiven: boolean;
enabled: boolean;
name: string;
newPassword: string;
hasPassword: boolean;
nameError: boolean;
nameErrorMessage: string;
passwordError: boolean;
passwordErrorMessage: string;
homeNetworkSsid: string;
homeNetworkError: boolean;
homeNetworkErrorMessage: string;
countryCode: string;
channel: string;
knownWifiNetworks: string[];
wifiChannels: WifiChannel[];
}
let gCountryCodeOptions: { id: string, label: string }[] | undefined = undefined;
const WifiConfigDialog = withStyles(styles, { withTheme: true })(
const WifiConfigDialog = withStyles(styles, { withTheme: true })(
class extends ResizeResponsiveComponent<WifiConfigProps, WifiConfigState> {
refName: React.RefObject<HTMLInputElement>;
refPassword: React.RefObject<HTMLInputElement>;
model: PiPedalModel;
refName: React.RefObject<HTMLInputElement>;
refPassword: React.RefObject<HTMLInputElement>;
model: PiPedalModel;
constructor(props: WifiConfigProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
fullScreen: false,
enabled: this.props.wifiConfigSettings.enable,
name: this.props.wifiConfigSettings.hotspotName,
newPassword: "",
nameError: false,
nameErrorMessage: NBSP,
passwordError: false,
showWifiWarningDialog: false,
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
passwordErrorMessage: NBSP,
countryCode: this.props.wifiConfigSettings.countryCode,
channel: this.props.wifiConfigSettings.channel,
wifiChannels: []
};
this.model.getWifiChannels(this.props.wifiConfigSettings.countryCode)
.then((wifiChannels_) => {
this.setState({wifiChannels: wifiChannels_});
}).catch((err)=> {});
this.refName = React.createRef<HTMLInputElement>();
this.refPassword = React.createRef<HTMLInputElement>();
}
mounted: boolean = false;
handleEnableChanged(e: any) {
this.setState({ enabled: e.target.checked });
}
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200 })
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
}
componentDidUpdate(prevProps: WifiConfigProps) {
if (this.props.open && !prevProps.open)
{
this.setState({
enabled: this.props.wifiConfigSettings.enable,
constructor(props: WifiConfigProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
showPassword: false,
fullScreen: false,
compactWidth: false,
compactHeight: false,
autoStartMode: this.props.wifiConfigSettings.autoStartMode,
name: this.props.wifiConfigSettings.hotspotName,
newPassword: "",
hasPassword: false,
nameError: false,
nameErrorMessage: NBSP,
passwordError: false,
homeNetworkSsid: this.props.wifiConfigSettings.homeNetwork,
homeNetworkError: false,
homeNetworkErrorMessage: NBSP,
showWifiWarningDialog: false,
showHelpDialog: false,
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
passwordErrorMessage: NBSP,
countryCode: this.props.wifiConfigSettings.countryCode,
channel: this.props.wifiConfigSettings.channel
});
channel: this.props.wifiConfigSettings.channel,
knownWifiNetworks: [],
wifiChannels: []
};
this.requestWifiChannels(this.props.wifiConfigSettings.countryCode);
this.requestKnownWifiNetworks();
if (this.props.wifiConfigSettings.countryCode !== this.state.countryCode)
{
this.model.getWifiChannels(this.props.wifiConfigSettings.countryCode)
.then((wifiChannels) => {
this.setState({wifiChannels: wifiChannels});
})
.catch((error) => {});
this.refName = React.createRef<HTMLInputElement>();
this.refPassword = React.createRef<HTMLInputElement>();
}
mounted: boolean = false;
}
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200, compactWidth: width < 600, compactHeight: height < 450 })
}
}
handleOk(wifiWarningGiven: boolean) {
let hasError = false;
if (this.state.enabled)
{
let name = this.state.name;
if (name.length === 0) {
this.setState({nameError: true, nameErrorMessage: "* Required"});
hasError = true;
} else if (name.length > 31)
{
this.setState({nameError: true, nameErrorMessage: "> 31 characters"});
componentDidMount() {
super.componentDidMount();
this.mounted = true;
this.applyDeferredWifiChannels();
this.applyDeferredWifiNetworks();
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
}
private deferredWifiNetworks?: string[] = undefined;
applyDeferredWifiNetworks() {
if (this.mounted && this.deferredWifiNetworks) {
this.setState({ knownWifiNetworks: this.deferredWifiNetworks });
this.deferredWifiNetworks = undefined;
}
}
requestKnownWifiNetworks() {
this.model.getKnownWifiNetworks()
.then((networks: string[]) => {
if (this.mounted) {
this.setState({ knownWifiNetworks: networks });
} else {
this.deferredWifiNetworks = networks;
}
}).catch(() => {
});
}
private lastChannelRequestCountryCode = "";
private deferredWifiChannels?: WifiChannel[] = undefined;
applyDeferredWifiChannels() {
if (this.mounted && this.deferredWifiChannels) {
this.setState({ wifiChannels: this.deferredWifiChannels });
this.deferredWifiChannels = undefined;
}
}
requestWifiChannels(countryCode: string) {
if (countryCode === this.lastChannelRequestCountryCode) {
return;
}
this.lastChannelRequestCountryCode = countryCode;
this.model.getWifiChannels(countryCode)
.then((wifiChannels) => {
if (this.mounted) {
this.setState({ wifiChannels: wifiChannels });
} else {
}
})
.catch((error) => {
this.lastChannelRequestCountryCode = ""; // try again later. :-/
});
}
componentDidUpdate(prevProps: WifiConfigProps) {
if (this.props.open && !prevProps.open) {
this.setState({
name: this.props.wifiConfigSettings.hotspotName,
newPassword: "",
autoStartMode: this.props.wifiConfigSettings.autoStartMode,
homeNetworkSsid: this.props.wifiConfigSettings.homeNetwork,
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
countryCode: this.props.wifiConfigSettings.countryCode,
channel: this.props.wifiConfigSettings.channel
});
this.requestKnownWifiNetworks();
this.requestWifiChannels(this.props.wifiConfigSettings.countryCode);
}
this.applyDeferredWifiChannels();
}
getDefaultPasswordValue(): string {
return (this.state.showPassword && this.state.newPassword === "") ?
"password" :
this.state.newPassword;
}
handleOk(wifiWarningGiven: boolean) {
let hasError = false;
if (this.state.autoStartMode !== 0) {
let name = this.state.name;
if (name.length === 0) {
this.setState({ nameError: true, nameErrorMessage: "* Required" });
hasError = true;
} else if (name.length > 31) {
this.setState({ nameError: true, nameErrorMessage: "> 31 characters" });
hasError = true;
}
let password = this.state.newPassword;
if (!this.props.wifiConfigSettings.hasSavedPassword && password.length === 0) {
this.setState({ passwordError: true, passwordErrorMessage: "* required" });
} else if (password.length > 0) {
if (password.length < 8) {
this.setState({ passwordError: true, passwordErrorMessage: "Less than 8 characters" });
hasError = true;
} else if (password.length > 63) {
this.setState({ passwordError: true, passwordErrorMessage: "> 63 characters" });
hasError = true;
}
}
if (this.state.autoStartMode === 2 && this.state.homeNetworkSsid.length === 0) {
this.setState({ homeNetworkError: true, homeNetworkErrorMessage: "* Required" });
hasError = true;
}
}
if (this.state.autoStartMode !== 0 && (!this.props.wifiConfigSettings.hasSavedPassword) && this.state.newPassword.length === 0) {
this.setState({ passwordError: true, passwordErrorMessage: "* Required" });
hasError = true;
}
let password = this.state.newPassword;
if (password.length > 0) {
if (password.length < 8) {
this.setState({passwordError: true, passwordErrorMessage: "Less than 8 characters"});
hasError = true;
if (!hasError) {
let wifiConfigSettings = new WifiConfigSettings();
wifiConfigSettings.valid = true;
} else if (password.length > 63) {
this.setState({passwordError: true, passwordErrorMessage: "> 63 characters"});
hasError = true;
wifiConfigSettings.autoStartMode = this.state.autoStartMode;
wifiConfigSettings.homeNetwork = this.state.homeNetworkSsid;
wifiConfigSettings.hotspotName = this.state.name;
wifiConfigSettings.countryCode = this.state.countryCode;
wifiConfigSettings.channel = this.state.channel;
if (this.state.newPassword.length === 0 || this.state.autoStartMode === 0) {
wifiConfigSettings.hasPassword = false;
} else {
wifiConfigSettings.hasPassword = true;
wifiConfigSettings.password = this.state.newPassword;
}
}
}
if (this.state.enabled && (!this.props.wifiConfigSettings.hasPassword) && this.state.newPassword.length === 0)
{
this.setState({passwordError: true, passwordErrorMessage: "* Required"});
hasError = true;
}
if (!hasError)
{
let wifiConfigSettings = new WifiConfigSettings();
wifiConfigSettings.valid = true;
wifiConfigSettings.enable = this.state.enabled;
wifiConfigSettings.hotspotName = this.state.name;
wifiConfigSettings.countryCode = this.state.countryCode;
wifiConfigSettings.channel = this.state.channel;
if (this.state.newPassword.length === 0 || !this.state.enabled)
{
wifiConfigSettings.hasPassword = false;
} else {
wifiConfigSettings.hasPassword = true;
wifiConfigSettings.password = this.state.newPassword;
}
if (!this.props.wifiConfigSettings.wifiWarningGiven && !wifiWarningGiven) {
this.setState({showWifiWarningDialog: true});
} else {
wifiConfigSettings.wifiWarningGiven = true;
this.preventPasswordPrompt();
// let preventPasswordPrompt changes settle (it's HARD to prevent a password prompt)
setTimeout(()=> {
if (!this.props.wifiConfigSettings.wifiWarningGiven && !wifiWarningGiven) {
this.setState({ showWifiWarningDialog: true });
} else {
wifiConfigSettings.wifiWarningGiven = true;
this.props.onOk(wifiConfigSettings);
},100);
}
}
}
}
preventPasswordPrompt()
{
let passwordInput = this.refPassword.current;
if (passwordInput)
{
passwordInput.type = "text";
passwordInput.value = "";
handleChannelChange(e: any) {
let value = e.target.value as string;
this.setState({ channel: value });
}
this.setState({newPassword: ""});
}
handleChannelChange(e: any)
{
let value = e.target.value as string;
this.setState({channel: value });
}
handleCountryChanged(e: any)
{
let value = e.target.value as string;
handleCountryChanged(e: any) {
let value = e.target.value as string;
this.setState({countryCode: value});
this.model.getWifiChannels(value)
.then(wifiChannels => {
this.setState({wifiChannels: wifiChannels});
})
.catch((error)=> { });
}
this.setState({ countryCode: value });
this.requestWifiChannels(value);
}
handleTogglePasswordVisibility() {
this.setState({ hasPassword: true, showPassword: !this.state.showPassword });
}
render() {
let props = this.props;
let classes = this.props.classes;
let { open, onClose} = props;
private homeNetworkSsidElement?: HTMLInputElement = undefined;
const handleClose = () => {
this.preventPasswordPrompt();
// let preventPasswordPrompt changes settle (it's HARD to prevent a password prompt)
setTimeout(()=> {
getCountryCodeValue(countryCode: string) {
let value = this.model.countryCodes[countryCode];
if (!value) return undefined;
return { label: value, id: countryCode };
}
getCountryCodeOptions(): { id: string, label: string }[] {
if (gCountryCodeOptions) {
return gCountryCodeOptions;
}
let result: { label: string, id: string }[] = [];
for (let key in this.model.countryCodes) {
let value = this.model.countryCodes[key];
result.push({ id: key, label: value });
}
result.sort((left, right) => {
return left.label.localeCompare(right.label);
});
if (result.length !== 0) {
gCountryCodeOptions = result;
}
return result;
}
render() {
let props = this.props;
let classes = this.props.classes;
let { open, onClose } = props;
const handleClose = () => {
onClose();
},100);
};
return (
<DialogEx tag="wifiConfig" open={open} fullWidth onClose={handleClose} style={{userSelect: "none"}}
fullScreen={this.state.fullScreen}
>
{ this.state.fullScreen && (
<DialogTitle>Wi-fi Hotspot</DialogTitle>
)}
<DialogContent>
<FormControlLabel
control={(
<Switch
checked={this.state.enabled}
onChange={(e: any) => this.handleEnableChanged(e)}
color="primary"
/>
)}
label="Enable"
/>
<TextField style={{ marginBottom: 16, marginTop: 16 }}
autoComplete="off"
id="name"
spellCheck="false"
label="SSID"
type="text"
fullWidth
error={this.state.nameError}
helperText={this.state.nameErrorMessage}
value={this.state.name}
onChange={(e) => this.setState({name: e.target.value, nameError: false, nameErrorMessage: NBSP})}
inputRef={this.refName}
disabled={!this.state.enabled}
/>
<div style={{ marginBottom: 16 }}>
<input type="password" style={{display:"none"}}/>
<NoChangePassword
inputRef={this.refPassword}
onPasswordChange={(text): void => { this.setState({ newPassword: text, passwordError: false, passwordErrorMessage: NBSP }); }}
hasPassword={this.props.wifiConfigSettings.hasPassword}
label="WEP Passphrase"
error={this.state.passwordError}
helperText={this.state.passwordErrorMessage}
defaultValue={this.state.newPassword}
disabled={!this.state.enabled}
/>
</div>
<div style={{ marginBottom: 16}}>
<FormControl>
<InputLabel htmlFor="countryCodeSelect">Country</InputLabel>
<Select variant="standard" id="countryCodeSelect" value={this.state.countryCode} style={{width: 220}}
onChange={(event)=>this.handleCountryChanged(event)} >
{Object.entries(this.model.countryCodes).map(([key,value])=> {
return (
<MenuItem value={key}>{value}</MenuItem>
);
})}
</Select>
</FormControl>
</div>
<div style={{ marginBottom: 24}}>
<FormControl>
<InputLabel htmlFor="channelSelect">Channel</InputLabel>
<Select variant="standard" id="channelSelect" value={this.state.channel} style={{width: 220}} onChange={(e)=>{
this.handleChannelChange(e);
}}>
{this.state.wifiChannels.map((channel)=> {
return (
<MenuItem value={channel.channelId}>{channel.channelName}</MenuItem>
)
})}
</Select>
</FormControl>
</div>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} variant="dialogSecondary" style={{ width: 120 }}>
Cancel
</Button>
<Button onClick={()=> this.handleOk(false)} variant="dialogPrimary" style={{ width: 120 }} >
OK
</Button>
</DialogActions>
<DialogEx open={this.state.showWifiWarningDialog} tag="wifiConfirm"
style={{userSelect: "none"}}>
};
let enabled = this.state.autoStartMode !== 0;
return (
<DialogEx tag="wifiConfig" open={open} fullWidth onClose={handleClose} style={{ userSelect: "none" }}
fullScreen={this.state.fullScreen}
>
{(this.state.fullScreen || !this.state.compactHeight) && (
<DialogTitle>Wi-fi Hotspot</DialogTitle>
)}
<DialogContent>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Enabling the Wi-Fi hotspot will disable regular Wi-Fi network access on the PiPedal device. Once
enabled, connect to the hotspot and launch http://172.23.0.2 or http://{this.state.name}.local to access the PiPedal web app again.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary" gutterBottom>
Are you sure you want to proceed?
</Typography>
<div style={
!this.state.compactWidth
? { display: "flex", gap: 16, flexDirection: "column", flexFlow: "nowrap", alignItems: "start" }
: { display: "block", gap: 16, flexDirection: "row", flexFlow: "nowrap" }
}
>
<div style={{
display: "flex", flexGrow: 1, flexBasis: 100,
gap: 16, flexDirection: "column", flexFlow: "nowrap", alignItems: "start"
}}
>
<FormControl variant="standard" style={{ flexGrow: 1, flexBasis: 1 }} >
<InputLabel htmlFor="behavior">Auto-start hotspot when</InputLabel>
<Select id="behavior" value={this.state.autoStartMode}
onChange={(el) => {
let value = el.target.value as number
this.setState({
autoStartMode: value
});
if (value === 2 && this.state.homeNetworkSsid.length === 0 && this.state.knownWifiNetworks.length !== 0) {
this.setState({
homeNetworkSsid: this.state.knownWifiNetworks[0]
});
if (this.homeNetworkSsidElement) {
this.homeNetworkSsidElement.value = this.state.knownWifiNetworks[0];
}
}
}}
>
<MenuItem value={0}>Never</MenuItem>
<MenuItem value={1}>No ethernet connection</MenuItem>
<MenuItem value={2}>Not at home</MenuItem>
<MenuItem value={3}>No remembered Wi-Fi connections</MenuItem>
<MenuItem value={4}>Always</MenuItem>
</Select>
<FormHelperText>{NBSP}</FormHelperText>
</FormControl>
{(this.state.compactWidth || this.state.autoStartMode !== 2) && (
<IconButton style={{ flexGrow: 0, flexShrink: 0, marginTop: 8 }}
onClick={() => { this.setState({ showHelpDialog: true }); }}
>
<HelpOutlineIcon />
</IconButton>
)}
</div>
<div style={{
display: this.state.autoStartMode === 2 ? "flex" : "none", gap: 16, flexGrow: 1, flexBasis: 100, flexDirection: "column", flexFlow: "nowrap", alignItems: "center",
}}>
<Autocomplete options={this.state.knownWifiNetworks}
freeSolo autoSelect={false} forcePopupIcon={true}
style={{
flexGrow: 1, marginLeft: this.state.compactWidth ? 32 : 0
}}
value={this.state.homeNetworkSsid}
onChange={(event, value): void => {
this.setState({ homeNetworkSsid: value?.toString() ?? "", homeNetworkError: false, homeNetworkErrorMessage: NBSP });
}
}
renderInput={
(params) =>
<TextField {...params} variant="standard" label="Home Network SSID"
autoComplete="off"
spellCheck="false"
error={this.state.homeNetworkError}
onChange={
(event) => {
this.setState({ homeNetworkSsid: event.target.value, homeNetworkError: false, homeNetworkErrorMessage: NBSP });
}
}
helperText={this.state.homeNetworkErrorMessage}
InputLabelProps={{
shrink: true
}}
/>}
/>
<IconButton style={{ visibility: this.state.compactWidth ? "hidden" : "visible", flexGrow: 0, flexShrink: 0 }}
onClick={() => { this.setState({ showHelpDialog: true }); }}
>
<HelpOutlineIcon />
</IconButton>
</div>
</div>
<div style={
!this.state.compactWidth
? { display: "flex", flexFlow: "row nowrap", gap: 24, alignItems: "start" }
: { display: "block" }
}>
<TextField variant="standard" style={{ marginBottom: 8, flexGrow: 1, flexBasis: 1 }}
autoComplete="off"
spellCheck="false"
error={this.state.nameError}
id="name"
label="SSID"
type="text"
fullWidth
helperText={this.state.nameErrorMessage}
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value, nameError: false, nameErrorMessage: NBSP })}
inputRef={this.refName}
InputLabelProps={{
shrink: true
}}
disabled={!enabled}
/>
<div style={{ marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
<form autoComplete='off' onSubmit={() => false}> {/*Prevents chrome from saving passwords */}
<TextField label="WEP passphrase" variant="standard"
autoComplete="off"
spellCheck="false"
fullWidth
error={this.state.passwordError}
onFocus={() => { this.setState({ hasPassword: true }) }}
type={this.state.showPassword ? "text" : "password"}
onChange={(event): void => {
this.setState({ hasPassword: true, newPassword: event.target.value.toString(), passwordError: false, passwordErrorMessage: NBSP });
}
}
helperText={this.state.passwordErrorMessage}
defaultValue={
this.getDefaultPasswordValue()
}
disabled={!enabled}
InputLabelProps={{
shrink: true
}}
InputProps={{
startAdornment:
this.props.wifiConfigSettings.hasSavedPassword && !this.state.hasPassword && !this.state.showPassword ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : ""
,
endAdornment: (
<IconButton size="small"
aria-label="toggle password visibility"
onClick={() => { this.handleTogglePasswordVisibility(); }}
>
{
(this.state.showPassword) ?
(
<VisibilityIcon fontSize="small" />
) :
(
<VisibilityOffIcon fontSize="small" />
)
}
</IconButton>
)
}}
/>
</form>
</div>
</div>
<div style={
!this.state.compactWidth
? { display: "flex", flexFlow: "row nowrap", gap: 24, alignItems: "start" }
: { display: "block" }
}>
<div style={{ display: "flex", marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
<Autocomplete fullWidth
defaultValue={this.getCountryCodeValue(this.state.countryCode)}
disableClearable={true}
onChange={(event, value) => { if (value) { this.setState({ countryCode: value.id }) } }}
options={this.getCountryCodeOptions()}
renderInput={(params) => (<TextField {...params} variant="standard" label="Regulatory Domain" />)}
/>
{/*
<Select variant="standard" label="Regulatory Domain" id="countryCodeSelect"
fullWidth value={this.state.countryCode} style={{}}
onChange={(event) => this.handleCountryChanged(event)} disabled={!enabled} >
{Object.entries(this.model.countryCodes).map(([key, value]) => {
return (
<MenuItem value={key}>{value}</MenuItem>
);
})}
</Select>
*/}
</div>
<div style={{ display: "flex", flexFlow: "column nowrap", marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
<FormControl style={{ flexGrow: 1 }} >
<InputLabel htmlFor="channelSelect">Channel</InputLabel>
<Select variant="standard" id="channelSelect" value={this.state.channel}
fullWidth
disabled={!enabled}
onChange={(e) => {
this.handleChannelChange(e);
}}>
{this.state.wifiChannels.map((channel) => {
return (
<MenuItem value={channel.channelId}>{channel.channelName}</MenuItem>
)
})}
</Select>
</FormControl>
</div>
</div>
</DialogContent>
<DialogActions>
<Button onClick={()=> this.setState({showWifiWarningDialog: false})} variant="dialogSecondary" style={{ width: 120 }}>
<Button onClick={handleClose} variant="dialogSecondary" style={{ width: 120 }}>
Cancel
</Button>
<Button onClick={()=> {
this.setState({showWifiWarningDialog: false});
this.handleOk(true);
}} variant="dialogPrimary" style={{ width: 120 }} >
PROCEED
<Button onClick={() => this.handleOk(false)} variant="dialogPrimary" style={{ width: 120 }} >
OK
</Button>
</DialogActions>
</DialogEx>
</DialogEx>
);
}
});
{this.state.showHelpDialog && (
<DialogEx open={this.state.showHelpDialog} tag="wifiHelp"
style={{ userSelect: "none" }}>
<DialogContent>
<Typography className={classes.pgraph} variant="h6" color="textPrimary">
PiPedal Auto-Hotspot
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
The PiPedal <b><i>Auto-Hotspot</i></b> feature allows you to connect to your Raspberry Pi even if you don't have
access to a Wi-Fi router. For example, if you are performing at a live venue, you probably will not
have access to a Wi-Fi router; but you can configure PiPedal so that your Raspberry Pi automatically
starts a Wi-Fi hotspot when you are not at home. The feature is primarily intended for use with the
PiPedal Android client, but you may find it useful for other purposes as well.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Raspberry Pi's are unable to run hotspots, and have another active Wi-Fi connection at the same time; so the auto-hotspot feature
automatically turns the hotspot on, when your Raspberry Pi cannot otherwise be connected to, and can be configured to
automatically turn the PiPedal hotspot off when you do want your Raspberry Pi to connect to another Wi-Fi access point.
Which auto-start option you should select depends on how you normally connect to your Raspberry Pi when you are at home.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
If you normally connect to your Raspberry Pi using an ethernet connection, the <b><i>No ethernet connection</i></b> is a
good choice. The PiPedal Wi-Fi hotspot will be available whenever the ethernet cable is unplugged. <b><i>Always on</i></b> is
also a good choice, but may confuse your phone or tablet, since your Android device will now have to decide whether to auto-connect to your home Wi-Fi
router, or to the Raspberry Pi hotspot. If you use the <b><i>No ethernet connection</i></b> option, your phone or tablet will
never see the PiPedal hotspot and your Wi-Fi router at the same time.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
If you normally connect to your Raspberry Pi through a Wi-Fi router, <b><i>Not at home</i></b> is a good choice. The
PiPedal hotspot will be automatically turned off whenever your home Wi-Fi router is in range, and automatically turned on
when you are out of range of your home Wi-Fi router.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
If there are multiple locations, and multiple Wi-Fi routers you use with PiPedal on a regular basis, you can select
the <b><i>No remembered Wi-Fi connections</i></b> option, but this is a riskier option. The PiPedal hotspot will be automatically turned on if there are no
Wi-Fi access points in range that you have previously connected to from your Raspberry Pi, and will be automatically turned on otherwise.
The risk is that you could find yourself unable to connect to your Raspberry Pi when performing
at a local bar, after you have used your Rasberry Pi to connect to the Wi-Fi access point at the coffee shop nextdoor. (Public Wi-Fi access
points usually won't work because devices that are connected to a public access point can't connect to each other).
Will you ever do that? Probably not. But there is some risk that you might find yourself unable to connect at a live venue. Whether that's an
acceptable risk is up to you.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Typically, when you're away from home, there's no easy way to connect to your Raspberry Pi from a laptop in order to
correct the problem. So you should carefully test that your auto-hotspot configuration works as expected before you adventure
away from home with PiPedal.
</Typography>
</DialogContent>
<DialogActions>
<Button
onClick={
() => {
this.setState({ showHelpDialog: false });
}}
variant="dialogSecondary" >
Ok
</Button>
</DialogActions>
</DialogEx>
)}
{this.state.showWifiWarningDialog && (
<DialogEx open={this.state.showWifiWarningDialog} tag="wifiConfirm"
style={{ userSelect: "none" }}>
<DialogContent>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Enabling the Wi-Fi hotspot will disable regular Wi-Fi network access on your Raspberry Pi when the PiPedal hotspot is active.
If you are relying on Wi-Fi access to connect to your Raspberry Pi, consider carefully whether your autostart options
will allow you to connect to your Raspberry Pi once applied. PiPedal
<a href="https://rerdavies.github.io/pipedal/Configuring.html" target="_blank" rel="noreferrer">
online documentation</a> provides a discussion of how to choose safe hotspot auto-start options.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
When you are connected to the PiPedal hotspot, you can connect to the PiPedal web server at http://10.48.0.1.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary" gutterBottom>
Are you sure you want to continue?
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => this.setState({ showWifiWarningDialog: false })} variant="dialogSecondary" style={{ width: 120 }}>
Cancel
</Button>
<Button onClick={() => {
this.setState({ showWifiWarningDialog: false });
this.handleOk(true);
}} variant="dialogPrimary" style={{ width: 120 }} >
PROCEED
</Button>
</DialogActions>
</DialogEx>
)}
</DialogEx >
);
}
});
export default WifiConfigDialog;
+11 -11
View File
@@ -21,10 +21,12 @@
export default class WifiConfigSettings {
deserialize(input: any) : WifiConfigSettings{
this.autoStartMode = input.autoStartMode;
this.hasSavedPassword = input.hasSavedPassword;
this.homeNetwork = input.homeNetwork;
this.valid = input.valid;
this.wifiWarningGiven = input.wifiWarningGiven;
this.rebootRequired = input.rebootRequired;
this.enable = input.enable;
this.hotspotName = input.hotspotName;
this.hasPassword = input.hasPassword;
this.password = input.password;
@@ -35,29 +37,27 @@ export default class WifiConfigSettings {
clone() : WifiConfigSettings {
return new WifiConfigSettings().deserialize(this);
}
valid: boolean = true;
autoStartMode: number = 0;
hasSavedPassword: boolean = false;
homeNetwork: string = "";
wifiWarningGiven: boolean = false;
enable: boolean = true;
hasPassword: boolean = false;
rebootRequired = false;
valid: boolean = false;
hotspotName: string = "pipedal";
password: string = "";
countryCode: string = "US";
channel: string = "g6";
channel: string = "0";
isEnabled() { return this.autoStartMode !== 0;}
getSummaryText() {
let result: string;
if (!this.valid) {
result = "Not available.";
} else if (!this.enable) {
} else if (this.autoStartMode === 0) {
result = "Disabled.";
} else {
result = this.hotspotName;
}
if (this.rebootRequired)
{
result += " (Restart required)";
}
return result;
}
+111 -100
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies
// Copyright (c) 2022-2024 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
@@ -1132,7 +1132,7 @@ static void PrintHelp()
PrettyPrinter pp;
pp << "pipedalconfig - Command-line post-install configuration for PiPedal"
<< "\n"
<< "Copyright (c) 2022 Robin Davies. All rights reserved."
<< "Copyright (c) 2022-2024 Robin Davies."
<< "\n"
<< "\n"
<< "See https://rerdavies.github.io/pipedal/Documentation.html for "
@@ -1157,10 +1157,10 @@ static void PrintHelp()
<< "\n"
<< HangingIndent() << " --install [--port <port#>]\t"
<< "Install or re-install services and service accounts."
<< "Install or re-install PiPedal services and service accounts."
<< "\n"
<< "\n"
<< "The --port option controls which port the web server uses."
<< "The --port option controls which TCP/IP port the web server uses."
<< "\n\n"
<< HangingIndent() << " --uninstall\t"
@@ -1169,68 +1169,54 @@ static void PrintHelp()
<< "\n"
<< HangingIndent() << " --enable\t"
<< "Start the pipedal service at boot time."
<< "Start PiPedal services at boot time."
<< "\n"
<< "\n"
<< HangingIndent() << " --disable\tDo not start the pipedal service at boot time."
<< HangingIndent() << " --disable\tDo not start PiPedal services at boot time."
<< "\n"
<< "\n"
<< HangingIndent() << " --start\tStart the pipedal services."
<< HangingIndent() << " --start\tStart PiPedal services."
<< "\n"
<< "\n"
<< HangingIndent() << " --stop\tStop the pipedal services."
<< HangingIndent() << " --stop\tStop PiPedal services."
<< "\n"
<< "\n"
<< HangingIndent() << " --restart\tRestart the pipedal services."
<< HangingIndent() << " --restart\tRestart PiPedal services."
<< "\n"
<< "\n"
<< HangingIndent() << " --enable-p2p [<country_code> <ssid> [[<pin>] <channel>] ]\t"
<< "Enable the P2P (Wi-Fi Direct) hotspot."
<< HangingIndent() << " --enable-hotspot\t <country_code> <ssid> <wep_password> <channel> [<hotspot-option>]"
<< "\n\nEnable Wi-Fi hotspot."
<< "\n\n"
<< "With no additional arguments, the P2P channel is enabled with most-recent settings."
<< "PiPedal's Wi-Fi hotspot allows you to connect to your Raspberry Pi when you don't have a Wi-Fi router, for example, when you are "
<< "performing away from home. It is most particularly useful when using the PiPedal Android client. Consult PiPedal's online documentation "
<< "for guidance on configuring a Wi-Fi hotspot."
<< "\n\n"
<< "<country_code> is the 2-letter ISO-3166 country code for "
"the country you are in. see below for further notes."
<< "One of the following hotspot options can be specifed. If no hotspot option is given, the hotspot will always be enabled. "
<< "If one of the hotspot options are given, the PiPedal server will turn the hotspot on or off automatically, as conditions change."
<< "\n\n"
<< "--home-network <wifi-ssid>\n"
<< AddIndent(4) << "Hotspot is disabled if the specificed Wi-Fi network is detected.\n"
<< AddIndent(-4)
<< "--no-ethernet\n"
<< AddIndent(4) << "Hotspot is disabled if an ethernet network is connected.\n"
<< AddIndent(-4)
<< "--no-wifi\n"
<< AddIndent(4) << "Hotspot is disabled if a remembered Wi-Fi network is detected.\n"
<< AddIndent(-4)
<< "\n"
<< "Caution: Wi-Fi connections are disabled when the hotspot is activated. If you currently access your Raspberry Pi using "
<< "Wi-Fi, choose your hotspot options carefully, to ensure that you can still access your Raspberry Pi."
<< "\n\n"
<< "<ssid> is the name you see when connecting. "
<< "country_code is the 2-letter ISO-3166 country code for "
<< "the country you are in. see below for further notes."
<< "\n\n"
<< "<pin> is an exactly-eight-digit pin number that you must "
<< "enter when connecting to the hotspot. If you don't "
<< "provide a pin, pipedalconfig will generate and "
<< "display a random pin for you. The pin is a "
<< "so-called \"label\" pin, which is the same every "
<< "time you are asked to enter it (unlike a keypad pin "
<< "which changes every time you need to enter it."
<< "\n\n"
<< "Consider attaching a label to the bottom of your device "
<< "so you can can remember the pin if you wan't to connect a new "
<< "device to PiPedal. (It's also available on the Settings page of PiPedal, if you have access to PiPedal UI on another device.)"
<< "\n\n"
<< "For best performance, the channel number should be 1, 6, or 11 (the Wifi Direct \"social\" channels). "
<< "Channel number defaults to 1."
<< "If the Wi-Fi channel is not specified, a Wi-Fi channel will be automatically selected."
<< "\n\n"
<< HangingIndent() << " --disable-p2p\tDisable Wi-Fi Direct access."
<< HangingIndent() << " --disable-hotspot\tDisabled the Wi-Fi hotspot."
<< "\n\n"
<< HangingIndent() << " --list-p2p-channels [<country_code>] \tList valid p2p channels for the current/specified country."
<< "\n\n"
// << HangingIndent() << " --enable-legacy-ap\t <country_code> <ssid> <wep_password> <channel>\tEnable a legacy Wi-Fi access point."
// << "\n\n"
// << "Enable a legacy Wi-Fi access point. \n\n"
// << "country_code is the 2-letter ISO-3166 country code for "
// << "the country you are in. see below for further notes."
// << "\n\n"
// << "See below for an explanation of when you might want to use a legacy Wi-Fi access point instead of Wifi-Direct access."
// << "an explanation of when you might want to use a legacy Access Point instead of "
// << "a P2P (Wi-Fi Direct) connection. Generally, you should prefer a P2p connection "
// << "to an ordinary Hotspot connection."
// << "\n\n"
<< HangingIndent() << " --disable-legacy-ap\tDisabled the legacy Wi-Fi access point."
<< HangingIndent() << " --list-wifi-channels [<country_code>] \tList valid Wifi channels for the current/specified country."
<< "\n\n"
<< Indent(0) << "Country codes:"
@@ -1244,28 +1230,11 @@ static void PrintHelp()
<< "with reduced amplitude and feature sets."
<< "\n\n"
<< "For the most part, Wi-Fi country codes are taken from the list of ISO 3661 "
<< "2-letter country codes; although there are a handful of exceptions for small "
<< "2-letter country codes; although there are a handful of exceptions for small "
<< "countries and islands. See the Alpha-2 code column of "
<< "\n\n"
<< Indent(8) << "https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes."
<< "\n\n"
<< Indent(0) << "Legacy Wi-Fi Access Points:"
<< "\n\n"
<< Indent(4)
<< "Some older devices may not be able to connect to PiPedal using Wi-Fi Direct connections. "
"Old Apple devices, for example, do not support Wi-Fi Direct. In theory, Wi-Fi Direct should "
"allow legacy Wi-Fi devices to connect to a Wi-Fi Direct access points as if it were an "
"ordinary Access Point. If this turns out "
"not to be the case, you can configure PiPedal to provide a legacy Wi-Fi access point instead. "
"\n\n"
"Unlike Wi-Fi Direct connections, legacy Access Points will prevent both the connecting device "
"and the PiPedal host machine from connecting to the Internet over a simultanous Wi-Fi connection Access "
"Point. On a connecting Android device, you won't be able to use the data connection either when a legacy "
"Wi-Fi connection is active."
"\n\n"
"Wi-Fi Direct connections are "
"therefore preferrable under almost all circumstances.\n\n";
<< "\n\n";
}
static int ListP2PChannels(const std::vector<std::string> &arguments)
@@ -1315,7 +1284,7 @@ int main(int argc, char **argv)
bool helpError = false;
bool stop = false, start = false;
bool enable = false, disable = false, restart = false;
bool enable_ap = false, disable_ap = false;
bool enable_hotspot = false, disable_hotspot = false;
bool enable_p2p = false, disable_p2p = false;
bool list_p2p_channels = false;
bool get_current_port = false;
@@ -1324,6 +1293,9 @@ int main(int argc, char **argv)
bool excludeShutdownService = false;
std::string prefixOption;
std::string portOption;
std::string homeNetwork;
bool noEthernet = false;
bool noWifi = false;
parser.AddOption("--nosudo", &nosudo); // strictly a debugging aid. Run without sudo, until we fail because of permissions.
parser.AddOption("--install", &install);
@@ -1337,22 +1309,17 @@ int main(int argc, char **argv)
parser.AddOption("--help", &help);
parser.AddOption("--prefix", &prefixOption);
parser.AddOption("--port", &portOption);
// parser.AddOption("--enable-legacy-ap", &enable_ap);
parser.AddOption("--disable-ap", &disable_ap);
parser.AddOption("--enable-p2p", &enable_p2p);
parser.AddOption("--disable-p2p", &disable_p2p);
parser.AddOption("--list-p2p-channels", &list_p2p_channels);
parser.AddOption("--fix-permissions", &fix_permissions);
parser.AddOption("--get-current-port", &get_current_port); // private. For debug use only.
parser.AddOption("--excludeShutdownService", &excludeShutdownService); // private (unstable) option used by shutdown service.
parser.AddOption("--enable-hotspot", &enable_hotspot);
parser.AddOption("--disable-hotspot", &disable_hotspot);
parser.AddOption("--home-network",&homeNetwork);
parser.AddOption("--no-ethernet",&noEthernet);
parser.AddOption("--no-wifi",&noWifi);
try
{
parser.Parse(argc, (const char **)argv);
int actionCount =
help + get_current_port + install + uninstall + stop + start + enable + disable + enable_ap + disable_ap + restart + enable_p2p + disable_p2p + list_p2p_channels + fix_permissions;
help + get_current_port + install + uninstall + stop + start + enable + disable + enable_hotspot + disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels + fix_permissions;
if (actionCount > 1)
{
throw std::runtime_error("Please provide only one action.");
@@ -1366,7 +1333,7 @@ int main(int argc, char **argv)
throw std::runtime_error("No action provided.");
}
if ((!enable_p2p) && (!enable_ap) && (!list_p2p_channels))
if ((!enable_p2p) && (!enable_hotspot) && (!list_p2p_channels))
{
if (parser.Arguments().size() != 0)
{
@@ -1374,6 +1341,23 @@ int main(int argc, char **argv)
helpError = true;
}
}
int hotspotOptionCount =
(homeNetwork.length() != 0 ? 1: 0)
+ noEthernet
+ noWifi;
if (enable_hotspot)
{
if (hotspotOptionCount > 1)
{
throw std::runtime_error("Only one hotspot option at a time can be specified.");
}
} else {
if (hotspotOptionCount > 0)
{
throw std::runtime_error("Hotspot options only only valid when the --enable-hotspot option has been supplied.");
}
}
}
catch (const std::exception &e)
{
@@ -1383,7 +1367,7 @@ int main(int argc, char **argv)
}
if (helpError)
{
cout << "\n";
cout << endl;
return EXIT_FAILURE; // don't scroll the error off the screen.
}
@@ -1483,21 +1467,23 @@ int main(int argc, char **argv)
}
else if (enable_p2p)
{
try
{
auto argv = parser.Arguments();
WifiDirectConfigSettings settings;
settings.ParseArguments(argv);
settings.valid_ = true;
settings.enable_ = true;
SetWifiDirectConfig(settings);
RestartService(true); // also have to retart web service so that it gets the correct device name.
}
catch (const std::exception &e)
{
cout << "ERROR: " << e.what() << endl;
return EXIT_FAILURE;
}
cout << "ERROR: Wi-Fi p2p connections are no longer supported. Use hotspots instead." << endl;
return EXIT_FAILURE;
// try
// {
// auto argv = parser.Arguments();
// WifiDirectConfigSettings settings;
// settings.ParseArguments(argv);
// settings.valid_ = true;
// settings.enable_ = true;
// SetWifiDirectConfig(settings);
// RestartService(true); // also have to retart web service so that it gets the correct device name.
// }
// catch (const std::exception &e)
// {
// cout << "ERROR: " << e.what() << endl;
// return EXIT_FAILURE;
// }
}
else if (disable_p2p)
{
@@ -1508,20 +1494,45 @@ int main(int argc, char **argv)
RestartService(true);
return EXIT_SUCCESS;
}
else if (enable_ap)
else if (enable_hotspot)
{
auto argv = parser.Arguments();
WifiConfigSettings settings;
settings.ParseArguments(argv);
HotspotAutoStartMode startMode = HotspotAutoStartMode::Always;
if (homeNetwork.length() != 0)
{
startMode = HotspotAutoStartMode::NotAtHome;
} else if (noEthernet)
{
startMode = HotspotAutoStartMode::NoEthernetConnection;
} else if (noWifi)
{
startMode = HotspotAutoStartMode::NoRememberedWifiConections;
}
settings.ParseArguments(argv,startMode,homeNetwork);
if (settings.hasPassword_)
{
settings.hasSavedPassword_ = true;
}
SetWifiConfig(settings);
if (silentSysExec(SYSTEMCTL_BIN " restart " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
{
throw std::runtime_error("Failed to restart the " PIPEDALD_SERVICE " service.");
}
}
else if (disable_ap)
else if (disable_hotspot)
{
WifiConfigSettings settings;
settings.valid_ = true;
settings.enable_ = false;
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Never;
SetWifiConfig(settings);
if (silentSysExec(SYSTEMCTL_BIN " restart " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
{
throw std::runtime_error("Failed to restart the " PIPEDALD_SERVICE " service.");
}
}
}
catch (const std::exception &e)
+159 -23
View File
@@ -29,6 +29,7 @@
#include "ServiceConfiguration.hpp"
#include <unordered_set>
#include <mutex>
#include <algorithm>
using namespace pipedal;
using namespace dbus::networkmanager;
@@ -51,6 +52,8 @@ namespace pipedal::impl
virtual void Reload() override;
virtual void Close() override;
std::vector<std::string> GetKnownWifiNetworks();
virtual PostHandle Post(PostCallback&&fn) override;
virtual PostHandle PostDelayed(const clock::duration&delay,PostCallback&&fn) override;
virtual bool CancelPost(PostHandle handle) override;
@@ -97,8 +100,14 @@ namespace pipedal::impl
void ReleaseNetworkManager();
void OnEthernetStateChanged(uint32_t state);
void OnWlanStateChanged(uint32_t state);
std::vector<ssid_t> GetAutoConnectSsids();
std::vector<ssid_t> GetKnownVisibleAccessPoints();
std::vector<AccessPoint::ptr> GetAllAccessPoints();
std::vector<ssid_t> GetAllAutoConnectSsids();
std::vector<ssid_t> GetKnownVisibleAccessPoints(const std::vector<ssid_t> &allAccessPoints);
void UpdateKnownNetworks(
std::vector<ssid_t> &knownSsids,
std::vector<AccessPoint::ptr> &allAccessPoints
);
void FireNetworkChanging();
@@ -126,9 +135,11 @@ namespace pipedal::impl
std::recursive_mutex networkChangingListenerMutex;
NetworkChangingListener networkChangingListener;
std::mutex knownWifiNetworksMutex;
std::vector<std::string> knownWifiNetworks;
bool ethernetConnected = true;
bool wlanConnected = true;
bool isAutoWlanConnectionVisible = true;
DBusEventHandle onDeviceAddedHandle = INVALID_DBUS_EVENT_HANDLE;
DBusEventHandle onDeviceRemovedHandle = INVALID_DBUS_EVENT_HANDLE;
@@ -190,7 +201,7 @@ void HotspotManagerImpl::onInitialize()
{
wifiConfigSettings.Load();
if (wifiConfigSettings.valid_ && wifiConfigSettings.enable_)
if (wifiConfigSettings.valid_ && wifiConfigSettings.IsEnabled())
{
onStartMonitoring();
}
@@ -445,7 +456,7 @@ void HotspotManagerImpl::onReload()
// ignore.
return;
case State::Disabled:
if (wifiConfigSettings.valid_ && wifiConfigSettings.enable_)
if (wifiConfigSettings.valid_ && wifiConfigSettings.IsEnabled())
{
onStartMonitoring();
}
@@ -487,6 +498,8 @@ void HotspotManagerImpl::Close()
}
}
HotspotManager::ptr HotspotManager::Create()
{
return std::make_unique<HotspotManagerImpl>();
@@ -508,37 +521,134 @@ public:
}
};
static std::string ssidToString(const std::vector<uint8_t> &ssid)
struct NetworkSortRecord {
std::string ssid;
bool connected = false;
bool knownNetwork = false;
bool visibleNetwork = false;
uint8_t strength;
};
void HotspotManagerImpl::UpdateKnownNetworks(
std::vector<ssid_t> &knownSsids,
std::vector<AccessPoint::ptr> & allAccessPoints
)
{
std::stringstream s;
for (auto v: ssid)
std::map<std::string,NetworkSortRecord> map;
for (auto &knownSsid: knownSsids)
{
if (v == 0) break; // breaks for some arguably legal ssids, but I can live with that.
s << (char)v;
std::string ssid = ssidToString(knownSsid);
if (ssid.length() != 0)
{
NetworkSortRecord &record = map[ssid];
record.ssid = ssid;
record.knownNetwork = true;
}
}
return s.str();
for (auto&accessPoint: allAccessPoints)
{
uint8_t strength = accessPoint->Strength();
auto vSsid = accessPoint->Ssid();
std::string ssid = ssidToString(vSsid);
if (ssid.length() != 0)
{
NetworkSortRecord&record = map[ssid];
record.ssid = ssid;
record.visibleNetwork = true;
record.strength = strength;
}
}
if (this->wlanDevice)
{
auto activeConnectionPath = this->wlanDevice->ActiveConnection();
if (activeConnectionPath.length() > 2) // "/" -> no connection. Be paranoid about "".
{
auto activeConnection = ActiveConnection::Create(dbusDispatcher,activeConnectionPath);
std::string activeSsid = activeConnection->Id();
auto it = map.find(activeSsid);
if (it != map.end())
{
it->second.connected = true;
}
}
}
std::vector<NetworkSortRecord> records;
records.reserve(map.size());
for (auto & mapEntry: map)
{
records.push_back(mapEntry.second);
}
std::sort(records.begin(),records.end(), [](const NetworkSortRecord&left, const NetworkSortRecord &right)
{
if (left.connected != right.connected)
{
return left.connected > right.connected;
}
if (left.knownNetwork != right.knownNetwork)
{
return left.knownNetwork > right.knownNetwork;
}
if (left.visibleNetwork != right.visibleNetwork)
{
return left.visibleNetwork > right.visibleNetwork;
}
if (left.strength != right.strength)
{
return left.strength > right.strength;
}
return false;
});
if (records.size() > 10)
{
records.resize(10);
}
std::vector<std::string> result;
result.reserve(records.size());
for (auto &record: records)
{
result.push_back(std::move(record.ssid));
}
{
std::lock_guard lock { this->knownWifiNetworksMutex };
this->knownWifiNetworks = std::move(result);
}
}
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetKnownVisibleAccessPoints()
std::vector<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints() {
std::vector<AccessPoint::ptr> accessPoints;
for (const auto &accessPointPath : wlanWirelessDevice->AccessPoints())
{
auto accessPoint = AccessPoint::Create(dbusDispatcher, accessPointPath);
accessPoints.push_back(std::move(accessPoint));
}
return accessPoints;
}
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetKnownVisibleAccessPoints(const std::vector<ssid_t>&allAccessPoints)
{
std::vector<std::vector<uint8_t>> knownSsids = this->GetAutoConnectSsids();
std::vector<std::vector<uint8_t>> knownSsids = this->GetAllAutoConnectSsids();
std::unordered_set<std::vector<uint8_t>, VectorHash<uint8_t>> index{knownSsids.begin(), knownSsids.end()};
std::vector<ssid_t> result;
for (const auto &accessPointPath : wlanWirelessDevice->AccessPoints())
std::vector<AccessPoint::ptr> accessPoints;
for (const auto &accessPoint : allAccessPoints)
{
auto accessPoint = AccessPoint::Create(dbusDispatcher, accessPointPath);
auto ssid = accessPoint->Ssid();
if (index.contains(ssid))
if (index.contains(accessPoint))
{
result.push_back(ssid);
result.push_back(accessPoint);
}
}
return result;
}
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetAutoConnectSsids()
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetAllAutoConnectSsids()
{
auto availableConnections = wlanDevice->AvailableConnections();
std::vector<std::vector<uint8_t>> ssids;
@@ -599,13 +709,31 @@ void HotspotManagerImpl::onAccessPointChanged()
}
}
static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessPoint::ptr>&accessPoints)
{
std::vector<std::vector<uint8_t>> result;
for (const auto&accessPoint: accessPoints)
{
try {
result.push_back(accessPoint->Ssid());
} catch (const std::exception&ignored)
{
// race to get a disappearing ssid. Ignore the error.
}
}
return result;
}
void HotspotManagerImpl::MaybeStartHotspot()
{
std::vector<std::vector<uint8_t>> connectableSsids = GetKnownVisibleAccessPoints(); // all the ssids currently visible for which we have credentials.
bool wantsHotspot = this->wifiConfigSettings.WantsHotspot(
this->ethernetConnected,connectableSsids);
std::vector<AccessPoint::ptr> allAccessPoints = GetAllAccessPoints();
std::vector<std::vector<uint8_t>> allAccessPointSsids = GetAccessPointSsids(allAccessPoints);
std::vector<std::vector<uint8_t>> connectableSsids = GetKnownVisibleAccessPoints(allAccessPointSsids); // all the ssids currently visible for which we have credentials.
this->UpdateKnownNetworks(connectableSsids,allAccessPoints);
this->isAutoWlanConnectionVisible = connectableSsids.size() != 0;
bool wantsHotspot = this->wifiConfigSettings.WantsHotspot(
this->ethernetConnected,connectableSsids,allAccessPointSsids);
if (this->state == State::Monitoring && wantsHotspot)
{
@@ -849,3 +977,11 @@ void HotspotManagerImpl::FireNetworkChanging() {
}
}
std::vector<std::string> HotspotManagerImpl::GetKnownWifiNetworks()
{
std::lock_guard lock(knownWifiNetworksMutex); // pushed off the service thread.
return this->knownWifiNetworks;
}
+3 -1
View File
@@ -30,7 +30,6 @@ namespace pipedal {
HotspotManager(HotspotManager&&) = delete;
HotspotManager & operator=(const HotspotManager&) = delete;
HotspotManager & operator=(const HotspotManager&&) = delete;
protected:
HotspotManager() {} // use Create().
@@ -47,6 +46,9 @@ namespace pipedal {
virtual void Reload() = 0;
virtual void Close() = 0;
virtual std::vector<std::string> GetKnownWifiNetworks() = 0;
using NetworkChangingListener = std::function<void(bool ethernetConnected,bool hotspotEnabled)>;
virtual void SetNetworkChangingListener(NetworkChangingListener &&listener) = 0;
+16 -1
View File
@@ -91,7 +91,7 @@ PiPedalModel::PiPedalModel()
OnNetworkChanging(ethernetConnected,hotspotEnabling);
}
);
hotspotManager->Open();
// don't actuall start the hotspotManager until after LV2 is initialized (in order to avoid logging oddities)
}
void PiPedalModel::Close()
@@ -2241,6 +2241,11 @@ static bool HasAlsaDevice(const std::vector<AlsaDeviceInfo> devices, const std::
return false;
}
void PiPedalModel::StartHotspotMonitoring()
{
this->hotspotManager->Open();
}
void PiPedalModel::WaitForAudioDeviceToComeOnline()
{
auto serverSettings = this->GetJackServerSettings();
@@ -2324,6 +2329,16 @@ void PiPedalModel::CancelNetworkChangingTimer()
}
}
std::vector<std::string> PiPedalModel::GetKnownWifiNetworks()
{
if (!this->hotspotManager)
{
return std::vector<std::string>();
}
return this->hotspotManager->GetKnownWifiNetworks();
}
void PiPedalModel::OnNetworkChanging(bool ethernetConnected,bool hotspotConnected)
{
CancelNetworkChangingTimer();
+3
View File
@@ -245,6 +245,8 @@ namespace pipedal
networkChangedListener = listener;
}
void StartHotspotMonitoring();
void WaitForAudioDeviceToComeOnline();
UpdateStatus GetUpdateStatus();
@@ -333,6 +335,7 @@ namespace pipedal
void SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings);
WifiConfigSettings GetWifiConfigSettings();
std::vector<std::string> GetKnownWifiNetworks();
void SetWifiDirectConfigSettings(const WifiDirectConfigSettings &wifiConfigSettings);
WifiDirectConfigSettings GetWifiDirectConfigSettings();
+6
View File
@@ -1025,6 +1025,12 @@ public:
std::vector<AlsaDeviceInfo> devices = model.GetAlsaDevices();
this->Reply(replyTo, "getAlsaDevices", devices);
}
else if (message == "getKnownWifiNetworks")
{
std::vector<std::string> channels = this->model.GetKnownWifiNetworks();
this->Reply(replyTo, "getWifiChannels", channels);
}
else if (message == "getWifiChannels")
{
std::string country;
+4 -1
View File
@@ -458,7 +458,10 @@ void PluginHost::Load(const char *lv2Path)
for (const std::string &s : messages)
{
Lv2Log::info("lilv: " + s);
if (s.length() != 0)
{
Lv2Log::info("lilv: " + s);
}
}
auto collator = Locale::GetInstance()->GetCollator();
+14
View File
@@ -63,6 +63,11 @@ namespace pipedal
this->indent = indent;
return *this;
}
PrettyPrinter&AddIndent(int64_t indent)
{
this->indent += indent;
return *this;
}
void WriteLine() {
lineBuffer.push_back('\n');
@@ -246,6 +251,15 @@ namespace pipedal
return pp;
};
}
pp_manip AddIndent(int n)
{
return [n] (PrettyPrinter&pp) ->PrettyPrinter& {
pp.AddIndent(n);
return pp;
};
}
pp_manip HangingIndent()
{
return [] (PrettyPrinter&pp) ->PrettyPrinter&{
+8 -1
View File
@@ -961,12 +961,19 @@ bool Storage::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings
{
copyToSave.hasPassword_ = previousValue.hasPassword_;
copyToSave.password_ = previousValue.hasPassword_;
copyToSave.hasSavedPassword_ = previousValue.hasPassword_;
} else {
if (copyToSave.IsEnabled())
{
copyToSave.hasSavedPassword_ = copyToSave.hasPassword_;
}
}
bool configChanged = wifiConfigSettings.ConfigurationChanged(previousValue);
bool configChanged = copyToSave.ConfigurationChanged(previousValue);
copyToSave.Save();
this->wifiConfigSettings = copyToSave;
this->wifiConfigSettings.hasPassword_ = false;
this->wifiConfigSettings.password_ = "";
return configChanged;
}
+2 -1
View File
@@ -41,6 +41,7 @@
#include "WebServer.hpp"
#include "Uri.hpp"
#include "ss.hpp"
#include <websocketpp/config/asio_no_tls.hpp>
@@ -1194,7 +1195,7 @@ namespace pipedal
}
catch (websocketpp::exception const &e)
{
std::cout << e.what() << std::endl;
Lv2Log::error(SS("Web server: " << e.what()));
}
if (this->signalOnDone != -1)
{
+10 -7
View File
@@ -29,19 +29,25 @@ int main(int argc, char**argv)
while (true)
{
std::string line;
std::cout << "e=enable,x=disable,q=quit > " << std::endl;;
std::cout << "e=no-etthernet,a=always,n=never,q=quit > " << std::endl;;
std::getline(std::cin,line);
if (line == "e")
{
settings.Load();
settings.enable_ = true;
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::NoEthernetConnection;
settings.Save();
hotspotManager->Reload();
} else if (line == "x")
} else if (line == "a")
{
settings.Load();
settings.enable_ = false;
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Always;
settings.Save();
hotspotManager->Reload();
} else if (line == "n")
{
settings.Load();
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Never;
settings.Save();
hotspotManager->Reload();
} else if (line == "q") {
@@ -53,9 +59,6 @@ int main(int argc, char**argv)
hotspotManager->Close();
hotspotManager = nullptr;
return EXIT_SUCCESS;
}
+1
View File
@@ -269,6 +269,7 @@ int main(int argc, char *argv[])
(unsigned long)getpid());
}
model.StartHotspotMonitoring();
model.WaitForAudioDeviceToComeOnline();
-2
View File
@@ -3,9 +3,7 @@ Installer:
sudo systemctl restart polkitd.service
- Scanning.
- verify that scanning doesn't cause overruns.
- UI changes.
- test behaviour when Osgilliad is offline.
- docs changes.
- unicode commandline arguments.