Merge remote-tracking branch 'origin/master' into msgStore

This commit is contained in:
GUVWAF 2024-03-05 20:23:42 +01:00
commit 4c6affbc7c
20 changed files with 655 additions and 192 deletions

36
.github/workflows/pdf.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: Generate Documentation PDF
on:
pull_request:
types:
- closed
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Get filename with date and sha
run: |
DATE_TIME=$(date +'%Y-%m-%d_%H-%M')
echo "filename=Meshtastic-Documentation-${DATE_TIME}-${{ github.sha }}.pdf" >> $GITHUB_OUTPUT
id: filename
- name: Install Prince
run: |
curl https://www.princexml.com/download/prince-15.3-linux-generic-x86_64.tar.gz -O
tar zxf prince-15.3-linux-generic-x86_64.tar.gz
cd prince-15.3-linux-generic-x86_64
yes "" | sudo ./install.sh
- name: Build PDF
run: npx docusaurus-prince-pdf -u https://meshtastic.org/docs/about/ --dest ./ --output ./${{ steps.filename.outputs.filename }}
- name: Create request artifacts
if: github.event.pull_request.merged
uses: gavv/pull-request-artifacts@v2.1.0
with:
commit: ${{ (github.event.pull_request_target || github.event.pull_request).head.sha }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
artifacts-token: ${{ secrets.ARTIFACTS_TOKEN }}
artifacts-repo: meshtastic/artifacts
artifacts-branch: docs
artifacts: ./${{ steps.filename.outputs.filename }}

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ build
.vercel
.env
.pnpm-debug.log
static/documents/pdf
# macOS
.DS_Store

View file

@ -20,6 +20,12 @@ export const Faq = {
content: "Everyone contributes in a different way. Join the [Meshtastic Discord](https://discord.gg/ktMAKGBnBs) and introduce yourself. We're all very friendly. If you'd like to pitch in some code, check out the [Development](/docs/developers) menu on the left. If you'd like to contribute financially, please visit our page on [Open Collective](https://opencollective.com/meshtastic) or you may choose to [sponsor a developer](https://github.com/sponsors/meshtastic). Check out [Contributing](/docs/contributing/) for more details.",
},
],
"firmware": [
{
title: "Can I update my node's firmware over the mesh?",
content: `No, Meshtastic does not support OTA updates over LoRa. Please visit [Flash Firmware](/docs/getting-started/flashing-firmware/) for update options.`,
},
],
"android": [
{
title: "What versions of Android does the Meshtastic Android App require?",
@ -192,6 +198,10 @@ If you use your ham radio license with Meshtastic, consider both the privileges
<FaqAccordion rows={Faq.general} />
## Firmware
<FaqAccordion rows={Faq.firmware} />
## Android Client
<FaqAccordion rows={Faq.android} />

View file

@ -17,7 +17,7 @@ Meshtastic is **not** LoRaWAN, Helium or TTN (TheThingsNetwork). Meshtastic uses
Power limits will generally be lifted in the software if `is_licensed` is set to `true`. See [HAM Mode](/docs/faq#amateur-radio-ham) for more information.
:::
## Channel Frequency Calculator
## Frequency Slot Calculator
<FrequencyCalculator />

View file

@ -7,10 +7,14 @@ sidebar_label: Paxcounter
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
The Paxcounter module counts the number of people passing by a specific area. It is commonly used in retail stores, museums, and other public spaces to monitor foot traffic and gather valuable data for analysis.
The Paxcounter module counts the number of people passing by a specific area by scanning for WiFi and BLE MAC addresses. It is commonly used in retail stores, museums, and other public spaces to monitor foot traffic and gather valuable data for analysis.
In order to use this module, make sure your devices have firmware version 2.2.17 or higher.
:::info
To operate the Paxcounter Module, it is mandatory to switch off both WiFi and Bluetooth in your Network and Bluetooth settings.
:::
## Paxcounter Module Config Values
### Enabled
@ -37,7 +41,11 @@ values={[
#### Android
:::info
No Paxcounter Module config options are available for Android.
Paxcounter Config options are available for Android.
1. Open the Meshtastic App
2. Navigate to: **Vertical Ellipsis (3 dots top right) > Radio Configuration > Paxcounter**
:::
</TabItem>
@ -81,7 +89,7 @@ meshtastic --get paxcounter
:::info
No Paxcounter module config options are available in the Web UI.
All Paxcounter module config options are available in the Web UI.
:::
</TabItem>

View file

@ -118,7 +118,7 @@ Store and Forward Config options are available for Android.
#### Apple
:::info
Store and Forward configuration is not currently available via the Apple clients.
All Store & Forward module config options are available on iOS, iPadOS and macOS at Settings > Module Configuration > Store & Forward.
:::
</TabItem>

View file

@ -46,6 +46,8 @@ How often we should send Device Metrics over the mesh.
Default is `900` seconds (15 minutes).
Device Metrics to a connected client app will always be sent once per minute, regardless of this setting.
### Environment Screen Enabled
Show the environment telemetry data on the device display.

View file

@ -30,8 +30,8 @@ The device config options are: Role, Serial Output, and Debug Log. Device config
This table shows the **default** values after selecting a preset. As always, individual settings can be adjusted after choosing a preset.
| Device Role | Bluetooth/Wi-Fi Enabled | Screen Enabled | Power Consumption | Retransmit | Prioritized Routing | Visible in Nodes List |
| -------------- | ----------------------- | -------------- | ----------------- | ---------- | ------------------- | --------------------- |
| Device Role | BLE/WiFi/Serial | Screen Enabled | Power Consumption | Retransmit | Prioritized Routing | Visible in Nodes List |
| -------------- | --------------- | -------------- | ----------------- | ---------- | ------------------- | --------------------- |
| CLIENT | Yes | Yes | Regular | Yes | No | Yes |
| CLIENT_MUTE | Yes | Yes | Lowest | No | No | Yes |
| CLIENT_HIDDEN | Yes | Yes | Lowest | Local only | No | No |
@ -40,10 +40,11 @@ This table shows the **default** values after selecting a preset. As always, ind
| SENSOR | Yes | No | High | No | No | Yes |
| TAK | Yes | Optional | Regular | Yes | No | Yes |
| TAK_TRACKER | Yes | Optional | Regular | Yes | No | Yes |
| ROUTER | No | No | High | Yes | Yes | Yes |
| ROUTER | No[^1] | No | High | Yes | Yes | Yes |
| ROUTER_CLIENT | Yes | Yes | Highest | Yes | Yes | Yes |
| REPEATER | Yes | No | High | Yes | Yes | No |
[^1]: The Router role enables [Power Saving](/docs/configuration/radio/power/#power-saving) by default. Consider ROUTER_CLIENT if BLE/WiFi/Serial are still needed.
### Rebroadcast Mode
@ -78,7 +79,7 @@ This is the GPIO pin number that will be used for the PWM buzzer, if your device
### Node Info Broadcast Seconds
This is the number of seconds between NodeInfo message broadcasts from the device. The device will still respond ad-hoc to NodeInfo messages when a response is wanted.
This is the number of seconds between NodeInfo message (containing i.a. long and short name) broadcasts from the device. The device will still respond ad-hoc to NodeInfo messages when a response is wanted. When the device hears any packet from a node it doesn't know yet, it will send its NodeInfo and ask for a response automatically.
### Double Tap as Button Press

View file

@ -100,13 +100,9 @@ Allows you to enable and disable transmit (TX) from the LoRa radio. Useful for h
Defaults to true
### Channel Number
### Frequency Slot
This is controlling the actual hardware frequency the radio is transmitting on. A channel number between 1 and NUM_CHANNELS (whatever the max is in the current region). If this is ZERO/UNSET then the rule is "use the old channel name hash based algorithm to derive the channel number".
:::info
LoRa Channel Configuration should not to be confused with messaging [Channel Configuration](/docs/configuration/radio/channels). See [Chat Channels VS Lora Modem Channels](/docs/configuration/tips#chat-channels-vs-lora-modem-channels) for further clarification.
:::
This setting controls the actual hardware frequency at which the radio transmits, represented by a frequency slot between 1 and NUM_SLOTS (the maximum for the current region and modem preset). If set to `0`/UNSET, the device reverts to the older channel name hash-based algorithm for determining the frequency slot.
### Ignore Incoming Array

View file

@ -4,25 +4,26 @@ title: Power Configuration
sidebar_label: Power
---
import Admonition from '@theme/Admonition';
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import calculateADC from "/src/utils/calculateADC";
:::info
:::info Use Device Config first
Power settings are advanced configuration, most users should choose a role under [Device Config](/docs/configuration/radio/device) to manage power for their device and shouldn't ever need to adjust these settings.
:::
The power config options are: Power Saving, Shutdown after losing power, ADC Multiplier Override, Wait Bluetooth Interval, Light Sleep Interval, Minimum Wake Interval, and Device Battery INA2xx Address. Power config uses an admin message sending a `Config.Power` protobuf.
:::info
ADC Multiplier, The Light Sleep setting only applies to ESP32-based boards. This settings will have no effect on nRF52/RP2040 modules.
:::
## Power Config Values
### Power Saving
If set, Bluetooth, Wifi, and screen (if applicable) are turned off. When using the Router device role, this setting is on by default. The assumption for a Router is that the device will be used in a standalone manner where these features are not needed, and will be shut off to conserve power. This is especially useful when a device is powered from a low-current source (i.e. solar).
:::warning
If enabled, modifications to settings can be made by waking the device through pressing the user button, resetting, or through the [admin channel](/docs/configuration/remote-admin/) for remote administration.
:::
When activated, this feature disables Bluetooth, Serial, WiFi, and the device's screen to conserve power. This is particularly beneficial for devices relying on low-current power sources, like solar panels. For details on which device roles have this feature enabled by default, please check the [Device Config](/docs/configuration/radio/device) section.
### Shutdown after losing power
@ -30,6 +31,10 @@ Automatically shut down a device after a defined time period if power is lost.
### ADC Multiplier Override
:::info ESP32 Only
This setting only applies to ESP32-based boards, it will have no effect on nRF52/RP2040 boards.
:::
Ratio of voltage divider for battery pin e.g. 3.20 (R1=100k, R2=220k)
Overrides the ADC_MULTIPLIER defined in the firmware device variant file for battery voltage calculation.
@ -44,9 +49,13 @@ Should be set to floating point value between 2 and 6
4. If "Battery Charge Percent" (e.g., B 3.82V 60%) is not displayed on the screen, it means that the default value of "Operative Adc Multiplier" is too high. Lower the "Operative Adc Multiplier" to a smaller number (it is recommended to decrease by 0.1) until the screen displays "Battery Charge Percent". Enter the current "Operative Adc Multiplier" in use into the "Operative Adc Multiplier" field in the calculator. Also, input the "Battery Charge Percent" displayed on the screen into the calculator.
5. Click the "Calculate" button to compute the "Calculated New Operative Adc Multiplier", and set it as the new "Operative Adc Multiplier" for the device.
:::tip ADC Calculator
<table>
<details>
<summary>ADC Calculator</summary>
<div>
<Admonition type="info">
<p>This calibration method only maps 4.2V to Battery Charge Percent 100%, and does not address the potential non-linearities of the ADC.</p>
</Admonition>
<table>
<tr>
<td> Battery Charge Percent: </td>
<td>
@ -81,12 +90,9 @@ Should be set to floating point value between 2 and 6
</button>
</td>
</tr>
</table>
:::
:::info
It's important to note that this calibration method only maps 4.2V to Battery Charge Percent 100%, and does not address the potential non-linearities of the ADC.
:::
</table>
</div>
</details>
### Wait Bluetooth Interval
@ -96,6 +102,10 @@ How long to wait before turning off BLE in no Bluetooth states
### Light Sleep Interval
:::info ESP32 Only
This setting only applies to ESP32-based boards, it will have no effect on nRF52/RP2040 boards.
:::
In light sleep the CPU is suspended, LoRa radio is on, BLE is off and GPS is on
`0` for default of five minutes
@ -110,7 +120,7 @@ While in light sleep when we receive packets on the LoRa radio we will wake and
If an INA-2XX device is auto-detected on one of the I2C buses at the specified address, it will be used as the authoritative source for reading device battery level voltage. Setting is ignored for devices with PMUs (e.g. T-beams)
:::tip
:::tip[Convert hexadecimal to decimal]
I2C addresses are normally represented in hexadecimal and will require conversion to decimal in order to set via Meshtastic clients. For example the I2C address of 0x40 converted to decimal is 64.
:::

View file

@ -39,7 +39,7 @@ values={[
# or
ID xxxx:xxxx QinHeng Electronics USB Single Serial
# or
FIXME (WISBLOCK OUTPUT)
ID xxxx:xxxx Adafruit WisCore RAK4631 Board
```
</TabItem>

View file

@ -1,6 +1,7 @@
---
id: devices
title: Devices
title: Devices | Supported Hardware Overview
description: Detailed documentation on Meshtastic supported devices, including hardware specifications, performance comparisons, and guides.
sidebar_label: Devices
sidebar_position: 1
---
@ -106,7 +107,7 @@ Inexpensive basic ESP32-based boards.
| Name | MCU | Radio | WiFi | BT | GPS |
|:------------------------------------------------------------------|:------------|:-------|:------------:|:---:|:---:|
| [LoRa32 V2.1](./heltec/?heltec=v2.1) | ESP32 | SX127x | 2.4GHz b/g/n | 4.2 | NO |
| [LoRa32 V3/3.1](./heltec/?heltec=v23) | ESP32 | SX1262 | 2.4GHz b/g/n | 4.2 | NO |
| [LoRa32 V3/3.1](./heltec/?heltec=v23) | ESP32-S3FN8 | SX1262 | 2.4GHz b/g/n | 5.0 | NO |
| [Wireless Stick Lite V3](./heltec/?heltec=Wireless+Stick+Lite+V3) | ESP32-S3FN8 | SX1262 | 2.4GHz b/g/n | 5.0 | NO |
| [Wireless Tracker](./heltec/?heltec=tracker) | ESP32-S3FN8 | SX1262 | 2.4GHz b/g/n | 5.0 | YES |
| [Wireless Paper](./heltec/?heltec=paper) | ESP32-S3FN8 | SX1262 | 2.4GHz b/g/n | 5.0 | NO |

View file

@ -15,11 +15,37 @@ To utilize this script you need to have an Adafruit IO account, a working mqtt b
:::
You will need to modify the script with your Adafruit IO and mqtt credentials and mqtt publisher node id
You will need to modify the ini sample file with your Adafruit IO and mqtt credentials
Code repository is here [https://bitbucket.org/tavdog/mesh_aio_logger/src/main/]
```ini
[GENERAL]
PRINT_CONFIG = false
; https://pynative.com/list-all-timezones-in-python/
TIMEZONE = US/Hawaii
CHANNEL_LIST = LongFast,Private
[MQTT]
SERVER = mqtt.server.net
PORT = 1883
USERNAME = a
PASSWORD = a
[AIO]
USER = a
KEY = a
;leave FEED_GROUP as Default is you don't want a separate group.
FEED_GROUP = Default
[LOG]
VOLTAGE = true
MESSAGE = true
POSITION = false
SNR = false
RSSI = false
```
```python
# Persistent mqtt client, watch for voltage, teleme, message packets and publish them to io
# feeds will be created. free tier maxes out at 10
# Persistent mqtt client, watch for voltage, telemetry, message packets and publish them to io
# Import Adafruit IO REST client.
from Adafruit_IO import Client, Feed, Data, RequestError
@ -29,107 +55,129 @@ import os
import sys
import json
import time
import random
import pytz
import configparser
config = configparser.ConfigParser()
# Read the configuration file
config.read('config.ini')
if config.getboolean('GENERAL','PRINT_CONFIG'):
# Iterate through sections and options to print all config values
for section in config.sections():
print(f"[{section}]")
for key, value in config.items(section):
print(f"{key} = {value}")
print() # Add an empty line between sections for better readability
# set your timezone here
timezone = pytz.timezone("US/Hawaii")
TIMEZONE = config['GENERAL']['TIMEZONE']
CHANNEL_LIST = config['GENERAL']['CHANNEL_LIST']
MQTT_SERVER = config['MQTT']['SERVER']
MQTT_PORT = int(config['MQTT']['PORT'])
MQTT_USERNAME = config['MQTT']['USERNAME']
MQTT_PASSWORD = config['MQTT']['PASSWORD']
AIO_USER = config['AIO']['USER']
AIO_KEY = config['AIO']['KEY']
AIO_FEED_GROUP = config['AIO']['FEED_GROUP'] # leave as Default is you don't want a separate group.
LOG_SNR = config.getboolean('LOG','SNR')
LOG_VOLTAGE = config.getboolean('LOG','VOLTAGE')
LOG_RSSI = config.getboolean('LOG','RSSI')
LOG_MESSAGE = config.getboolean('LOG','MESSAGE')
LOG_POSITION = config.getboolean('LOG','POSITION')
# set your Adafruit IO creds here
AIO_USER = "tavdog"
AIO_KEY = "XXXXXXXXXXXXX"
###### END SETTINGS ######
# set your MQTT broker information
MQTT_SERVER = "XXXXXXXXXX"
MQTT_PORT = 1883 # default
MQTT_TOPIC_PREFIX = "mesh" # specified when you setup your node to publish mqtt
MQTT_USERNAME = "xxxxxxxxx"
MQTT_PASSWORD = "xxxxxxxxx"
# your node ID that is publishingn to the MQTT broker
MQTT_NODE_ID = "!387e0248"
# your channel (node must be set to json output)
MQTT_CHANNEL = "LongFast"
print("\n\n")
client_id = str(random.randint(0, 100))
mqttClient = mqtt.Client("python_mesh_client_%s" % client_id) # this needs to be kind of unique. mqtt broker will not allow duplicate connections.
my_timezone = pytz.timezone(TIMEZONE)
channel_array = CHANNEL_LIST.split(',')
print("\n")
mqttClient = mqtt.Client("mesh_client_rssi")
mqttClient.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
mqttClient.connect(MQTT_SERVER, MQTT_PORT)
mqttClient.loop_start()
print("Subscribing to %s/2/json/%s/%s" % (MQTT_TOPIC_PREFIX, MQTT_CHANNEL,MQTT_NODE_ID))
mqttClient.subscribe("%s/2/json/%s/%s" % (MQTT_TOPIC_PREFIX, MQTT_CHANNEL,MQTT_NODE_ID))
time.sleep(1)
# you can add more channels here
#mqttClient.subscribe("%s/2/json/MauiMesh/%s" % (MQTT_TOPIC_PREFIX, MQTT_NODE_ID))
aio = Client(AIO_USER,AIO_KEY)
# bootstrap your node_db here if you want
#node_db = dict()
node_db = { 947782216: 'G1 Kula',
1839130823: 'HR-Haleakala Repeater',
1969840052: 'MR8-Magnet Rak 8dbi',
}
aio = Client(AIO_USER, AIO_KEY)
node_db = dict()
# bootstrap your node_db here if you want. otherwise it will populate eventually but die on restart.
# node_db = { 634792740: 'Tavis Blue',
# 947782216: 'G1 Wailuku',
# 1839130823: 'Giggle Hill',
# 2330585902: 'Ayla Kekona',
# 634717328: 'Nani Hoku',
# 3667835576: 'Rachael'}
# get feed object or create it if it doesn't exist yet.
def get_feed(full_name,kind):
name = full_name.split('-')[0]
#print("getting feed: " + name)
if "\\x00" in name or "\\u0000" in name or "\x00" in name: # json don't like emojis
name = full_name.split('-')[1].replace(' ','-').replace('.','-')
try:
feed = aio.feeds(f"mesh.{name.lower()}-{kind}")
feed = aio.feeds(f"{AIO_FEED_GROUP}.{name.lower()}-{kind}")
except:
print("creating feed:" + kind)
print("creating feed:" + f"{AIO_FEED_GROUP}.{name.lower()}-{kind}")
# Create Feed
new_feed = Feed(name=f"{name}_{kind}")
feed = aio.create_feed(feed=new_feed,group_key="mesh")
feed = aio.create_feed(feed=new_feed,group_key=AIO_FEED_GROUP)
return feed
def publish_rssi(data,metadata):
name = node_db[data['from']]
feed = get_feed(name,"rssi") # use a function here because we will create the feed if it doens't exist already.
print(feed.key + " \t\t: " + str(data['rssi']))
#print(feed.key + " \t\t: " + str(data['rssi']))
aio.send_data(feed.key, data['rssi'],metadata)
def publish_snr(data,metadata):
name = node_db[data['from']]
feed = get_feed(name,"snr") # use a function here because we will create the feed if it doens't exist already.
print(feed.key + " \t\t: " + str(data['snr']))
#print(feed.key + " \t\t: " + str(data['snr']))
aio.send_data(feed.key, data['snr'],metadata)
def publish_voltage(data,metadata):
name = node_db[data['from']]
feed = get_feed(name,"voltage") # use a function here because we will create the feed if it doens't exist already. print(feed.key + " \t: " + str(data['payload'].get('voltage',"none")))
print(feed.key + " \t: " + str(data['payload']['voltage']))
#print(feed.key + " \t: " + str(data['payload']['voltage']))
aio.send_data(feed.key, data['payload'].get('voltage',0),metadata)
def publish_packet(data):
feed = aio.feeds("mesh.messages")
feed = aio.feeds(AIO_FEED_GROUP+".messages")
if (data['from'] in node_db):
data['fname'] = node_db[data['from']]
if (data['to'] in node_db):
data['tname'] = node_db[data['to']]
# trim down the data. we really only want to see the message content
if 'stamp' in data:
stamp = datetime.fromtimestamp(data['timestamp'],my_timezone).strftime('%Y-%m-%d %H:%M:%S')
else:
current_time = datetime.now() # Assuming UTC time
# Convert to the desired timezone (e.g., 'America/Los_Angeles' or your preferred timezone)
current_time = current_time.astimezone(my_timezone)
stamp = current_time.strftime('%Y-%m-%d %H:%M:%S')
trimmed = {
'from' : data.get('fname',None) or data.get('from'),
'message' : data['payload']['text'],
'stamp' : datetime.fromtimestamp(data['timestamp'],timezone).strftime('%Y-%m-%d %H:%M:%S'),
'to' : data.get('tname',None) or data.get('to'),
'channel' : data['channel'],
'message' : data['payload'],
'stamp' : stamp,
}
aio.send_data(feed.key, json.dumps(trimmed, indent=4))
print(trimmed)
def on_message(client, userdata, message):
print("\n")
print("message received " ,str(message.payload.decode("utf-8")))
print("\n")
try:
data = json.loads(str(message.payload.decode("utf-8")))
except Exception as e:
print(e)
return
# check the topic of the message
if data['type'] == "text":
# publish all message packets to the packet log
if data['type'] == "text" and LOG_MESSAGE:
# publish all message packets to the message log
print(data)
try:
publish_packet(data)
except Exception as e:
print("error in publish:",e)
# update node_db if needed
if data['type'] == 'nodeinfo':
@ -141,34 +189,49 @@ def on_message(client, userdata, message):
# "payload":{"altitude":113,"latitude_i":208759687,"longitude_i":-1565037665
metadata = None
if data['type'] == 'position':
if data['type'] == 'position' and LOG_POSITION:
metadata = {
'lat': data['payload']['latitude_i'] / 10000000, #40.726190,
'lon': data['payload']['longitude_i'] / 10000000, #-74.005334,
'ele': data['payload'].get('altitude',None),
'created_at': datetime.now(timezone),
'created_at': str(datetime.now(my_timezone)),
}
# add to the node_db if we know who they are already
# if metadata:
# print(metadata)
if data['from'] in node_db:
try:
if 'rssi' in data and data['rssi'] != 0:
# do the doings
if LOG_RSSI and 'rssi' in data and data['rssi'] != 0:
publish_rssi( data, metadata )
if 'snr' in data and data['snr'] != 0:
if LOG_SNR and 'snr' in data and data['snr'] != 0:
publish_snr( data, metadata )
if 'payload' in data and data['payload'].get('voltage',0) != 0:
if LOG_VOLTAGE and 'payload' in data and 'voltage' in data['payload'] and data['payload'].get('voltage',0) != 0:
publish_voltage( data, metadata )
pass
except Exception as e:
print("Error sending to IO:", str(e))
mqttClient.on_message = on_message
# loop forever
#def on_log(client, userdata, level, buf):
#print("log: ",buf)
#mqttClient.on_log=on_log
while(True):
pass
if (not mqttClient.is_connected()) :
print("Connecting to mqtt server")
mqttClient.connect(MQTT_SERVER, MQTT_PORT)
mqttClient.loop_start()
for channel in channel_array:
print("Subscribing to %s" % channel)
mqttClient.subscribe("mesh/2/json/%s/#" % (channel))
time.sleep(1)
time.sleep(.01)
```

View file

@ -7,11 +7,17 @@ sidebar_position: 4
### Home Assistant Integrations for Meshtastic
:::warning Note
Due to a known issue with nRF52 devices and JSON, nRF52 devices are not supported for use with Home Assistant at this time.
:::
Integrating Meshtastic into Home Assistant brings a new level of control and monitoring to your mesh network. On this page, we'll guide you through the process of creating Meshtastic MQTT sensor entities within Home Assistant. Whether you want to keep an eye on battery levels, environmental conditions, or even receive notifications from your mesh network, these integrations provide you with the tools to make it happen.
:::info
It is highly recommended to download MQTT Explorer for analyzing the JSON threads that come across the broker. This can be downloaded here: http://mqtt-explorer.com/
It is highly recommended to download MQTT Explorer for analyzing the JSON threads that come across the broker. This can be downloaded here [http://mqtt-explorer.com/](http://mqtt-explorer.com/).
:::
@ -46,7 +52,7 @@ sensor:
value_json.payload.temperature is not defined %}
{{ (value_json.payload.voltage | float) | round(2) }}
{% else %}
{{ states('sensor.node_1_battery_voltage') }}
{{ this.state }}
{% endif %}
unit_of_measurement: "Volts"
# Telemetry packets come in two flavors: The default node telemetry, and the I2C sensor data.
@ -60,8 +66,9 @@ sensor:
{% if value_json.from == 4038675309 and value_json.payload.battery_level is defined %}
{{ (value_json.payload.battery_level | float) | round(2) }}
{% else %}
{{ states('sensor.node_1_battery_percent') }}
{{ this.state }}
{% endif %}
device_class: "battery"
unit_of_measurement: "%"
- name: "Node 1 ChUtil"
@ -72,7 +79,7 @@ sensor:
{% if value_json.from == 4038675309 and value_json.payload.channel_utilization is defined %}
{{ (value_json.payload.channel_utilization | float) | round(2) }}
{% else %}
{{ states('sensor.node_1_chutil') }}
{{ this.state }}
{% endif %}
unit_of_measurement: "%"
@ -84,7 +91,7 @@ sensor:
{% if value_json.from == 4038675309 and value_json.payload.air_util_tx is defined %}
{{ (value_json.payload.air_util_tx | float) | round(2) }}
{% else %}
{{ states('sensor.node_1_airutiltx') }}
{{ this.state }}
{% endif %}
unit_of_measurement: "%"
@ -103,9 +110,12 @@ sensor:
{% if value_json.from == 4038675309 and value_json.payload.temperature is defined %}
{{ (((value_json.payload.temperature | float) * 1.8) +32) | round(2) }}
{% else %}
{{ states('sensor.node_1_temperature') }}
{{ this.state }}
{% endif %}
device_class: "temperature"
unit_of_measurement: "F"
# With device_class: "temperature" set, make sure to use the configured unit for temperature in your HA instance.
# If you don't, then non-temperature messages will change the value of this sensor by reinterpreting the current state with the wrong unit, unless you account for it.
# For Celsius use: {{ (value_json.payload.temperature | float) | round(1) }}
# For Fahrenheit use: {{ (((value_json.payload.temperature | float) * 1.8) +32) | round(2) }}
@ -117,8 +127,9 @@ sensor:
{% if value_json.from == 4038675309 and value_json.payload.relative_humidity is defined %}
{{ (value_json.payload.relative_humidity | float) | round(2) }}
{% else %}
{{ states('sensor.node_1_humidity') }}
{{ this.state }}
{% endif %}
device_class: "humidity"
unit_of_measurement: "%"
- name: "Node 1 Pressure"
@ -129,9 +140,12 @@ sensor:
{% if value_json.from == 4038675309 and value_json.payload.barometric_pressure is defined %}
{{ (value_json.payload.barometric_pressure | float) | round(2) }}
{% else %}
{{ states('sensor.node_1_pressure') }}
{{ this.state }}
{% endif %}
device_class: "pressure"
unit_of_measurement: "hPa"
# With device_class: "pressure" set, make sure to use the configured unit for pressure in your HA instance.
# For psi use: {{ ((value_json.payload.barometric_pressure | float) / 68.9475729) | round(2) }}
- name: "Node 1 Gas Resistance"
unique_id: "node_1_gas_resistance"
@ -141,7 +155,7 @@ sensor:
{% if value_json.from == 4038675309 and value_json.payload.gas_resistance is defined %}
{{ (value_json.payload.gas_resistance | float) | round(2) }}
{% else %}
{{ states('sensor.node_1_gas_resistance') }}
{{ this.state }}
{% endif %}
unit_of_measurement: "MOhms"
```
@ -158,7 +172,7 @@ sensor:
{% if value_json.from == 4038675309 and value_json.payload.text is defined %}
{{ value_json.payload.text }}
{% else %}
{{ states('sensor.node_1_messages') }}
{{ this.state }}
{% endif %}
```
@ -184,7 +198,7 @@ Copy and paste these entities then change `name`, `unique_id`, `from`, and `stat
{% if value_json.from == 695318008 and value_json.payload.text is defined %}
{{ value_json.payload.text }}
{% else %}
{{ states('sensor.node_2_messages') }}
{{ this.state }}
{% endif %}
```

View file

@ -12,6 +12,23 @@ This section covers using the "meshtastic" command line executable, which displa
The `meshtastic` command is not run within python but is a script run from your operating system shell prompt. When you type "meshtastic" and the prompt is unable to find the command in Windows, check that the python "scripts" directory [is in your path](https://datatofish.com/add-python-to-windows-path).
:::
## Viewing Serial Output
The `--noproto` command in the Meshtastic Python CLI is used to disable the API and function merely as a "dumb serial terminal." This mode of operation allows both the API and device functionalities to remain accessible for regular use, while simultaneously providing a window into the raw serial output. This feature can be particularly useful for debugging, development, or understanding the low-level communication between devices.
```shellsession title="Example Usage"
user@host % meshtastic --noproto
# You should see results similar to this:
WARNING file:mesh_interface.py _sendToRadio line:681 Not sending packet because protocol use is disabled by noProto
Connected to radio
WARNING file:mesh_interface.py _sendPacket line:531 Not sending packet because protocol use is disabled by noProto
INFO | 18:38:04 711 [DeviceTelemetryModule] (Sending): air_util_tx=0.116361, channel_utilization=1.916667, battery_level=101, voltage=4.171000
DEBUG | 18:38:04 711 [DeviceTelemetryModule] updateTelemetry LOCAL
DEBUG | 18:38:04 711 [DeviceTelemetryModule] Node status update: 2 online, 4 total
INFO | 18:38:04 711 [DeviceTelemetryModule] Sending packet to phone
INFO | 18:38:04 711 Telling client we have new packets 28
```
## Getting a list of User Preferences
You can get a list of user preferences by running '--get' with an invalid attribute such as 'all'.

View file

@ -102,6 +102,9 @@ const config = {
},
},
},
prism: {
additionalLanguages: ["shell-session", "bash"],
},
},
plugins: [
() => {

View file

@ -9,7 +9,8 @@
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"serve": "docusaurus serve",
"clear": "docusaurus clear"
"clear": "docusaurus clear",
"pdf": "npx docusaurus-prince-pdf -u http://localhost:3000/docs/about/ --dest ./static/documents/pdf --output ./static/documents/pdf/Meshtastic-Documentation.pdf"
},
"dependencies": {
"@algolia/client-search": "^4.22.1",
@ -44,6 +45,7 @@
"@types/node": "^20.11.19",
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"docusaurus-prince-pdf": "^1.2.1",
"typescript": "^5.3.3"
}
}

View file

@ -97,6 +97,9 @@ devDependencies:
'@types/react-dom':
specifier: ^18.2.19
version: 18.2.19
docusaurus-prince-pdf:
specifier: ^1.2.1
version: 1.2.1
typescript:
specifier: ^5.3.3
version: 5.3.3
@ -2719,7 +2722,6 @@ packages:
/@sindresorhus/is@5.6.0:
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
engines: {node: '>=14.16'}
dev: false
/@slorber/remark-comment@1.0.0:
resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==}
@ -2896,7 +2898,6 @@ packages:
engines: {node: '>=14.16'}
dependencies:
defer-to-connect: 2.0.1
dev: false
/@tailwindcss/typography@0.5.10(tailwindcss@3.4.1):
resolution: {integrity: sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==}
@ -2910,6 +2911,11 @@ packages:
tailwindcss: 3.4.1
dev: true
/@tootallnate/once@2.0.0:
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
engines: {node: '>= 10'}
dev: true
/@trysound/sax@0.2.0:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
@ -3025,7 +3031,6 @@ packages:
/@types/http-cache-semantics@4.0.4:
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
dev: false
/@types/http-errors@2.0.4:
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
@ -3310,6 +3315,11 @@ packages:
/@xtuc/long@4.2.2:
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
/abab@2.0.6:
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
deprecated: Use your platform's native atob() and btoa() methods instead
dev: true
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@ -3347,6 +3357,15 @@ packages:
engines: {node: '>= 10.0.0'}
dev: false
/agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
dependencies:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
/aggregate-error@3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
@ -3500,6 +3519,10 @@ packages:
resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==}
hasBin: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
/at-least-node@1.0.0:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'}
@ -3701,7 +3724,6 @@ packages:
/cacheable-lookup@7.0.0:
resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
engines: {node: '>=14.16'}
dev: false
/cacheable-request@10.2.14:
resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==}
@ -3714,7 +3736,6 @@ packages:
mimic-response: 4.0.0
normalize-url: 8.0.0
responselike: 3.0.0
dev: false
/call-bind@1.0.7:
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
@ -3899,6 +3920,15 @@ packages:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
/cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/clone-deep@4.0.1:
resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==}
engines: {node: '>=6'}
@ -3947,6 +3977,13 @@ packages:
engines: {node: '>=10'}
dev: false
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: true
/comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
@ -4358,6 +4395,13 @@ packages:
css-tree: 1.1.3
dev: false
/cssstyle@3.0.0:
resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==}
engines: {node: '>=14'}
dependencies:
rrweb-cssom: 0.6.0
dev: true
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@ -4656,6 +4700,15 @@ packages:
lodash-es: 4.17.21
dev: false
/data-urls@4.0.0:
resolution: {integrity: sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==}
engines: {node: '>=14'}
dependencies:
abab: 2.0.6
whatwg-mimetype: 3.0.0
whatwg-url: 12.0.1
dev: true
/dayjs@1.11.10:
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
dev: false
@ -4686,6 +4739,10 @@ packages:
dependencies:
ms: 2.1.2
/decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
dev: true
/decode-named-character-reference@1.0.2:
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
dependencies:
@ -4696,7 +4753,6 @@ packages:
engines: {node: '>=10'}
dependencies:
mimic-response: 3.1.0
dev: false
/deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
@ -4718,7 +4774,6 @@ packages:
/defer-to-connect@2.0.1:
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
engines: {node: '>=10'}
dev: false
/define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
@ -4763,6 +4818,11 @@ packages:
robust-predicates: 3.0.2
dev: false
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: true
/depd@1.1.2:
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
engines: {node: '>= 0.6'}
@ -4837,6 +4897,21 @@ packages:
'@leichtgewicht/ip-codec': 2.0.4
dev: false
/docusaurus-prince-pdf@1.2.1:
resolution: {integrity: sha512-8/ssMwm60bDP9MSsFIlcnKPXVpclLh/VPRA01dosx3/1Pt1OcFfy5fkRSL2WBOSxEoVZcWr+oPzbeimlRJqfNA==}
hasBin: true
dependencies:
got: 13.0.0
jsdom: 22.1.0
tough-cookie: 4.1.3
yargs: 17.7.2
transitivePeerDependencies:
- bufferutil
- canvas
- supports-color
- utf-8-validate
dev: true
/dom-converter@0.2.0:
resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==}
dependencies:
@ -4863,6 +4938,14 @@ packages:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
dev: false
/domexception@4.0.0:
resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==}
engines: {node: '>=12'}
deprecated: Use your platform's native DOMException instead
dependencies:
webidl-conversions: 7.0.0
dev: true
/domhandler@4.3.1:
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
engines: {node: '>= 4'}
@ -4972,7 +5055,6 @@ packages:
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
dev: false
/error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@ -5357,7 +5439,15 @@ packages:
/form-data-encoder@2.1.4:
resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==}
engines: {node: '>= 14.17'}
dev: false
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/format@0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
@ -5420,6 +5510,11 @@ packages:
engines: {node: '>=6.9.0'}
dev: false
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
/get-intrinsic@1.2.4:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
@ -5438,7 +5533,6 @@ packages:
/get-stream@6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
dev: false
/github-slugger@1.5.0:
resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
@ -5555,6 +5649,23 @@ packages:
responselike: 3.0.0
dev: false
/got@13.0.0:
resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==}
engines: {node: '>=16'}
dependencies:
'@sindresorhus/is': 5.6.0
'@szmarczak/http-timer': 5.0.1
cacheable-lookup: 7.0.0
cacheable-request: 10.2.14
decompress-response: 6.0.0
form-data-encoder: 2.1.4
get-stream: 6.0.1
http2-wrapper: 2.2.1
lowercase-keys: 3.0.0
p-cancelable: 3.0.0
responselike: 3.0.0
dev: true
/graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
dev: false
@ -5768,6 +5879,13 @@ packages:
wbuf: 1.7.3
dev: false
/html-encoding-sniffer@3.0.0:
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
engines: {node: '>=12'}
dependencies:
whatwg-encoding: 2.0.0
dev: true
/html-entities@2.4.0:
resolution: {integrity: sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==}
dev: false
@ -5857,7 +5975,6 @@ packages:
/http-cache-semantics@4.1.1:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
dev: false
/http-deceiver@1.2.7:
resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==}
@ -5888,6 +6005,17 @@ packages:
resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==}
dev: false
/http-proxy-agent@5.0.0:
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
engines: {node: '>= 6'}
dependencies:
'@tootallnate/once': 2.0.0
agent-base: 6.0.2
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
/http-proxy-middleware@2.0.6(@types/express@4.17.21):
resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==}
engines: {node: '>=12.0.0'}
@ -5924,7 +6052,16 @@ packages:
dependencies:
quick-lru: 5.1.1
resolve-alpn: 1.2.1
dev: false
/https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
dependencies:
agent-base: 6.0.2
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
/human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
@ -5943,7 +6080,6 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: false
/icss-utils@5.1.0(postcss@8.4.35):
resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
@ -6177,6 +6313,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
dev: true
/is-reference@3.0.2:
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
dependencies:
@ -6297,6 +6437,44 @@ packages:
argparse: 2.0.1
dev: false
/jsdom@22.1.0:
resolution: {integrity: sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==}
engines: {node: '>=16'}
peerDependencies:
canvas: ^2.5.0
peerDependenciesMeta:
canvas:
optional: true
dependencies:
abab: 2.0.6
cssstyle: 3.0.0
data-urls: 4.0.0
decimal.js: 10.4.3
domexception: 4.0.0
form-data: 4.0.0
html-encoding-sniffer: 3.0.0
http-proxy-agent: 5.0.0
https-proxy-agent: 5.0.1
is-potential-custom-element-name: 1.0.1
nwsapi: 2.2.7
parse5: 7.1.2
rrweb-cssom: 0.6.0
saxes: 6.0.0
symbol-tree: 3.2.4
tough-cookie: 4.1.3
w3c-xmlserializer: 4.0.0
webidl-conversions: 7.0.0
whatwg-encoding: 2.0.0
whatwg-mimetype: 3.0.0
whatwg-url: 12.0.1
ws: 8.16.0
xml-name-validator: 4.0.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: true
/jsesc@0.5.0:
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
hasBin: true
@ -6310,7 +6488,6 @@ packages:
/json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
dev: false
/json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
@ -6340,7 +6517,6 @@ packages:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
dependencies:
json-buffer: 3.0.1
dev: false
/khroma@2.1.0:
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
@ -6484,7 +6660,6 @@ packages:
/lowercase-keys@3.0.0:
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/lru-cache@10.2.0:
resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
@ -7373,12 +7548,10 @@ packages:
/mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
dev: false
/mimic-response@4.0.0:
resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/mini-css-extract-plugin@2.8.0(webpack@5.90.2):
resolution: {integrity: sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==}
@ -7510,7 +7683,6 @@ packages:
/normalize-url@8.0.0:
resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==}
engines: {node: '>=14.16'}
dev: false
/npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
@ -7529,6 +7701,10 @@ packages:
boolbase: 1.0.0
dev: false
/nwsapi@2.2.7:
resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
dev: true
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@ -7602,7 +7778,6 @@ packages:
/p-cancelable@3.0.0:
resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
engines: {node: '>=12.20'}
dev: false
/p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
@ -7727,7 +7902,6 @@ packages:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
dependencies:
entities: 4.5.0
dev: false
/parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
@ -8355,6 +8529,10 @@ packages:
ipaddr.js: 1.9.1
dev: false
/psl@1.9.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
dev: true
/punycode@1.4.1:
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
dev: false
@ -8377,6 +8555,10 @@ packages:
side-channel: 1.0.5
dev: false
/querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
dev: true
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -8389,7 +8571,6 @@ packages:
/quick-lru@5.1.1:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
dev: false
/randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -8841,6 +9022,11 @@ packages:
strip-ansi: 6.0.1
dev: false
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
/require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@ -8852,11 +9038,9 @@ packages:
/requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: false
/resolve-alpn@1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
dev: false
/resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
@ -8880,7 +9064,6 @@ packages:
engines: {node: '>=14.16'}
dependencies:
lowercase-keys: 3.0.0
dev: false
/retry@0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
@ -8902,6 +9085,10 @@ packages:
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
dev: false
/rrweb-cssom@0.6.0:
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
dev: true
/rtl-detect@1.1.2:
resolution: {integrity: sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==}
dev: false
@ -8942,12 +9129,18 @@ packages:
/safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
/sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: false
/saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
dependencies:
xmlchars: 2.2.0
dev: true
/scheduler@0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
@ -9460,6 +9653,10 @@ packages:
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
/symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true
/tailwindcss@3.4.1:
resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==}
engines: {node: '>=14.0.0'}
@ -9580,6 +9777,23 @@ packages:
engines: {node: '>=6'}
dev: false
/tough-cookie@4.1.3:
resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==}
engines: {node: '>=6'}
dependencies:
psl: 1.9.0
punycode: 2.3.1
universalify: 0.2.0
url-parse: 1.5.10
dev: true
/tr46@4.1.1:
resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
engines: {node: '>=14'}
dependencies:
punycode: 2.3.1
dev: true
/trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@ -9747,6 +9961,11 @@ packages:
unist-util-is: 6.0.0
unist-util-visit-parents: 6.0.1
/universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
dev: true
/universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
@ -9809,6 +10028,13 @@ packages:
webpack: 5.90.2
dev: false
/url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
dependencies:
querystringify: 2.2.0
requires-port: 1.0.0
dev: true
/use-sync-external-store@1.2.0(react@18.2.0):
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:
@ -9883,6 +10109,13 @@ packages:
unist-util-stringify-position: 4.0.0
vfile-message: 4.0.2
/w3c-xmlserializer@4.0.0:
resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
engines: {node: '>=14'}
dependencies:
xml-name-validator: 4.0.0
dev: true
/watchpack@2.4.0:
resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
engines: {node: '>=10.13.0'}
@ -9904,6 +10137,11 @@ packages:
resolution: {integrity: sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==}
dev: false
/webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
dev: true
/webpack-bundle-analyzer@4.10.1:
resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
engines: {node: '>= 10.13.0'}
@ -10070,6 +10308,26 @@ packages:
engines: {node: '>=0.8.0'}
dev: false
/whatwg-encoding@2.0.0:
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
engines: {node: '>=12'}
dependencies:
iconv-lite: 0.6.3
dev: true
/whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
dev: true
/whatwg-url@12.0.1:
resolution: {integrity: sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==}
engines: {node: '>=14'}
dependencies:
tr46: 4.1.1
webidl-conversions: 7.0.0
dev: true
/which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true
@ -10147,7 +10405,6 @@ packages:
optional: true
utf-8-validate:
optional: true
dev: false
/xdg-basedir@5.1.0:
resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==}
@ -10161,6 +10418,20 @@ packages:
sax: 1.3.0
dev: false
/xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
dev: true
/xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
dev: true
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: true
/yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
dev: false
@ -10178,6 +10449,24 @@ packages:
resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
engines: {node: '>= 14'}
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: true
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
dependencies:
cliui: 8.0.1
escalade: 3.1.2
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
dev: true
/yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}

View file

@ -356,12 +356,12 @@ export const FrequencyCalculator = (): JSX.Element => {
</div>
<div className="flex gap-2 mb-4">
<label className="font-semibold">Number of channels:</label>
<label className="font-semibold">Number of slots:</label>
<input type="number" disabled={true} value={numChannels} />
</div>
<div className="flex gap-2">
<label>Channel:</label>
<label>Frequency Slot:</label>
<select
value={channel}
onChange={(e) => setChannel(Number.parseInt(e.target.value))}
@ -375,7 +375,7 @@ export const FrequencyCalculator = (): JSX.Element => {
</div>
<div className="flex gap-2">
<label className="font-semibold">Channel Frequency:</label>
<label className="font-semibold">Frequency of slot:</label>
<input type="number" disabled={true} value={channelFrequency} />
</div>
</div>

View file

@ -229,3 +229,13 @@ td {
padding-left: 10px !important;
padding-right: 10px !important;
}
/* Need all tab content to show for PDF generation */
@media print {
ul.tabs {
display: none;
}
.tabs-container div > div {
display: block;
}
}