add a decibel conversion command (#250)

fixes #231
This commit is contained in:
classabbyamp 2020-09-27 17:52:52 -04:00 committed by GitHub
parent be042a9641
commit 77b572eb3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 203 additions and 1 deletions

View File

@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Maps for CQ Zones, ITU Zones, ITU Regions, and Canadian prefixes.
- Attribution for all maps.
- Option to append ` | ?help` to the playing status.
- `?dbconv` command to convert voltage, power, and antenna gain values.
### Changed
- ARRL/RAC section maps to include all current ARRL/RAC sections.
### Fixed

201
exts/dbconv.py Normal file
View File

@ -0,0 +1,201 @@
"""
Conversion extension for qrm
---
Copyright (C) 2020 Abigail Gold, 0x5c
This file is part of qrm2 and is released under the terms of
the GNU General Public License, version 2.
"""
import math
from enum import Enum
from typing import Optional
import discord.ext.commands as commands
import common as cmn
from data import options as opt
# not sure why but UnitConverter and Unit need to be defined before DbConvCog and convert()
class UnitConverter(commands.Converter):
async def convert(self, ctx: commands.Context, argument: str):
try:
return Unit(argument)
except ValueError as e:
raise commands.BadArgument(message=str(e))
class Unit:
def __init__(self, raw: str):
self.raw: str = raw
self.unit: str
self.type: UnitType
self.is_db: bool
self.mult: int
self._parse()
def _parse(self):
s = self.raw.lower()
if len(s) > 2 and s[:2] == "db":
self.is_db = True
if s[2:] in units:
u = units[s[2:]]
self.mult = u["mult"]
self.unit = u["log"]
self.type = u["type"]
elif s in units:
self.is_db = False
u = units[s]
self.mult = u["mult"]
self.unit = u["scalar"]
self.type = u["type"]
else:
raise ValueError(f"Invalid unit: {self.raw}")
def __str__(self):
return self.unit
class UnitType(Enum):
voltage = 1
power = 2
antenna = 3
class DbConvCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="dbconv", aliases=["dbc"], category=cmn.cat.ref)
async def _db_conv(self, ctx: commands.Context,
value: Optional[float] = None,
unit_from: Optional[UnitConverter] = None,
unit_to: Optional[UnitConverter] = None):
"""
Convert between decibels and scalar values for voltage, power, and antenna gain.
**Valid Units**
*Voltage:* V, mV, µV, uV, dBV, dBmV, dBµV, dBuV
*Power:* fW, mW, W, kW, dBf, dBm, dBW, dBk
*Antenna Gain:* dBi, dBd, dBq
"""
embed = cmn.embed_factory(ctx)
if value is not None and unit_from is not None and unit_to is not None:
converted = convert(value, unit_from, unit_to)
embed.title = f"{value:.3g} {unit_from} = {converted:.3g} {unit_to}"
embed.colour = cmn.colours.good
else:
embed.title = "Decibel Quick Reference"
embed.description = (
"Decibels are a great way to easily represent large quantities that are common in electronics. "
"There are a few main types that are used often in radio: voltage, power, and antenna gain. "
"Here are some commonly-used reference levels for each type:"
)
v_db_info = ("**dBV** = relative to 1 V\n"
"**dBmV** = relative to 1 mV (1e-3 V)\n"
"**dBµV** = relative to 1 µV (1e-6 V)")
embed.add_field(name="Voltage Decibels", value=v_db_info, inline=False)
p_db_info = ("**dBW** = relative to 1 W\n"
"**dBk** = relative to 1 kW (1e3 W)\n"
"**dBm** = relative to 1 mW (1e-3 W)\n"
"**dBf** = relative to 1 fW (1e-15 W)")
embed.add_field(name="Power Decibels", value=p_db_info, inline=False)
a_db_info = ("**dBi** = relative to a theoretical __i__sotropic radiator in free space "
"(equal radiation in all directions)\n"
"**dBd** = relative to a dipole in free space (0 dBd = 2.15 dBi)\n"
"**dBq** = relative to a quarter-wave antenna in free space (0 dBq = -0.85 dBi)")
embed.add_field(name="Antenna Gain Decibels", value=a_db_info, inline=False)
embed.add_field(name="Use the bot to do the conversions",
value=f"`{opt.display_prefix}dbconv [value] [unit_from] [unit_to]`",
inline=False)
await ctx.send(embed=embed)
def setup(bot: commands.Bot):
bot.add_cog(DbConvCog(bot))
def convert(initial: float, unit1: Unit, unit2: Unit):
if unit1.type == unit2.type:
# dB to dB
if unit1.is_db and unit2.is_db:
if unit1.mult == unit2.mult:
return initial
elif unit1.type == UnitType.voltage:
return _calc_volt_db(_calc_volt(initial, unit1.mult), unit2.mult)
elif unit1.type == UnitType.power:
return _calc_power_db(_calc_power(initial, unit1.mult), unit2.mult)
elif unit1.type == UnitType.antenna:
return initial + (unit1.mult - unit2.mult)
# V/W to V/W
elif not unit1.is_db and not unit2.is_db:
if unit1.mult == unit2.mult:
return initial
return initial * unit1.mult / unit2.mult
# dB to V/W
elif unit1.is_db and not unit2.is_db:
if unit1.type == UnitType.voltage:
return _calc_volt(initial, unit1.mult) / unit2.mult
elif unit1.type == UnitType.power:
return _calc_power(initial, unit1.mult) / unit2.mult
# V/W to dB
elif not unit1.is_db and unit2.is_db:
if unit1.type == UnitType.voltage:
return _calc_volt_db(initial * unit1.mult, unit2.mult)
elif unit1.type == UnitType.power:
return _calc_power_db(initial * unit1.mult, unit2.mult)
raise ValueError(f"Can't convert between {unit1} and {unit2}")
units = {
# voltage
"uv": {"mult": 1e-6, "scalar": "µV", "log": "dBµV", "type": UnitType.voltage},
"µv": {"mult": 1e-6, "scalar": "µV", "log": "dBµV", "type": UnitType.voltage},
"mv": {"mult": 1e-3, "scalar": "mV", "log": "dBmV", "type": UnitType.voltage},
"v": {"mult": 1, "scalar": "V", "log": "dBV", "type": UnitType.voltage},
# power
"fw": {"mult": 1e-15, "scalar": "fW", "log": "dBf", "type": UnitType.power},
"f": {"mult": 1e-15, "scalar": "fW", "log": "dBf", "type": UnitType.power},
"mw": {"mult": 1e-3, "scalar": "mW", "log": "dBm", "type": UnitType.power},
"m": {"mult": 1e-3, "scalar": "mW", "log": "dBm", "type": UnitType.power},
"w": {"mult": 1, "scalar": "W", "log": "dBW", "type": UnitType.power},
"kw": {"mult": 1e3, "scalar": "kW", "log": "dBk", "type": UnitType.power},
"k": {"mult": 1e3, "scalar": "kW", "log": "dBk", "type": UnitType.power},
# antenna
"q": {"mult": -0.85, "scalar": None, "log": "dBq", "type": UnitType.antenna},
"i": {"mult": 0, "scalar": None, "log": "dBi", "type": UnitType.antenna},
"d": {"mult": 2.15, "scalar": None, "log": "dBd", "type": UnitType.antenna},
}
def _calc_power_db(p: float, ref: float):
return 10 * math.log10(p / ref)
def _calc_power(db: float, ref: float):
return 10 ** (db / 10) * ref
def _calc_volt_db(v: float, ref: float):
return 20 * math.log10(v / ref)
def _calc_volt(db: float, ref: float):
return 10 ** (db / 20) * ref
# testing code
if __name__ == "__main__":
while(True):
try:
ip = input("> ").split()
initial = float(ip[0])
unit1 = Unit(ip[1])
unit2 = Unit(ip[2])
conv = convert(initial, unit1, unit2)
print(f"{initial:.2f} {unit1} = {conv:.2f} {unit2}")
except ValueError as e:
print(e)

View File

@ -30,7 +30,7 @@ debug = False
owners_uids = (200102491231092736,)
# The extensions to load when running the bot.
exts = ["ae7q", "base", "fun", "grid", "ham", "image", "lookup", "morse", "qrz", "study", "weather"]
exts = ["ae7q", "base", "fun", "grid", "ham", "image", "lookup", "morse", "qrz", "study", "weather", "dbconv"]
# Either "time", "random", or "fixed" (first item in statuses)
status_mode = "fixed"