BMP280 Programming Notes v0.1
penzu link: https://penzu.com/p/05db0200
Contents
BMP280 Adapter
BMP280 I2C Device
BMP280 Driver
pyromori adapter tlfong01 2020may20hkt1501
pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/i2cdevice $ ls
adapter.py __init__.py __pycache__
pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/i2cdevice $ cat adapter.py
class Adapter:
“””
Must implement `_decode()` and `_encode()`.
“””
def _decode(self, value):
raise NotImplementedError
def _encode(self, value):
raise NotImplementedError
class LookupAdapter(Adapter):
“””Adaptor with a dictionary of values.
:param lookup_table: A dictionary of one or more key/value pairs where the key is the human-readable value and the value is the bitwise register value
“””
def __init__(self, lookup_table, snap=True):
self.lookup_table = lookup_table
self.snap = snap
def _decode(self, value):
for k, v in self.lookup_table.items():
if v == value:
return k
raise ValueError(“{} not in lookup table”.format(value))
def _encode(self, value):
if self.snap and type(value) in [int, float]:
value = min(list(self.lookup_table.keys()), key=lambda x: abs(x – value))
return self.lookup_table[value]
class U16ByteSwapAdapter(Adapter):
“””Adaptor to swap the bytes in a 16bit integer.”””
def _byteswap(self, value):
return (value >> 8) | ((value & 0xFF) << 8)
def _decode(self, value):
return self._byteswap(value)
def _encode(self, value):
return self._byteswap(value)
pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/i2cdevice $
pyromori i2c_device tlfong01 2020may20hkt1501
pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/i2cdevice $ ls
adapter.py __init__.py __pycache__
pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/i2cdevice $ cat __init__.py
from collections import namedtuple
__version__ = ‘0.0.6’
def _mask_width(value, bit_width=8):
“””Get the width of a bitwise mask
ie: 0b000111 = 3
“””
value >>= _trailing_zeros(value, bit_width)
return value.bit_length()
def _leading_zeros(value, bit_width=8):
“””Count leading zeros on a binary number with a given bit_width
ie: 0b0011 = 2
Used for shifting around values after masking.
“””
count = 0
for _ in range(bit_width):
if value & (1 << (bit_width – 1)):
return count
count += 1
value <<= 1
return count
def _trailing_zeros(value, bit_width=8):
“””Count trailing zeros on a binary number with a given bit_width
ie: 0b11000 = 3
Used for shifting around values after masking.
“””
count = 0
for _ in range(bit_width):
if value & 1:
return count
count += 1
value >>= 1
return count
def _int_to_bytes(value, length, endianness=’big’):
try:
return value.to_bytes(length, endianness)
except AttributeError:
output = bytearray()
for x in range(length):
offset = x * 8
mask = 0xff << offset
output.append((value & mask) >> offset)
if endianness == ‘big’:
output.reverse()
return output
class MockSMBus:
def __init__(self, i2c_bus, default_registers=None):
self.regs = [0 for _ in range(255)]
if default_registers is not None:
for index in default_registers.keys():
self.regs[index] = default_registers.get(index)
def write_i2c_block_data(self, i2c_address, register, values):
self.regs[register:register + len(values)] = values
def read_i2c_block_data(self, i2c_address, register, length):
return self.regs[register:register + length]
class _RegisterProxy(object):
“””Register Proxy
This proxy catches lookups against non existent get_fieldname and set_fieldname methods
and converts them into calls against the device’s get_field and set_field methods with
the appropriate options.
This means device.register.set_field(value) and device.register.get_field(value) will work
and also transparently update the underlying device without the register or field objects
having to know anything about how data is written/read/stored.
“””
def __init__(self, device, register):
self.device = device
self.register = register
def __getattribute__(self, name):
if name.startswith(“get_”):
name = name.replace(“get_”, “”)
return lambda: self.device.get_field(self.register.name, name)
if name.startswith(“set_”):
name = name.replace(“set_”, “”)
return lambda value: self.device.set_field(self.register.name, name, value)
return object.__getattribute__(self, name)
def write(self):
return self.device.write_register(self.register.name)
def read(self):
return self.device.read_register(self.register.name)
def __enter__(self):
self.device.read_register(self.register.name)
self.device.lock_register(self.register.name)
return self
def __exit__(self, exception_type, exception_value, exception_traceback):
self.device.unlock_register(self.register.name)
class Register():
“””Store information about an i2c register”””
def __init__(self, name, address, fields=None, bit_width=8, read_only=False, volatile=True):
self.name = name
self.address = address
self.bit_width = bit_width
self.read_only = read_only
self.volatile = volatile
self.is_read = False
self.fields = {}
for field in fields:
self.fields[field.name] = field
self.namedtuple = namedtuple(self.name, sorted(self.fields))
class BitField():
“””Store information about a field or flag in an i2c register”””
def __init__(self, name, mask, adapter=None, bit_width=8, read_only=False):
self.name = name
self.mask = mask
self.adapter = adapter
self.bit_width = bit_width
self.read_only = read_only
class BitFlag(BitField):
def __init__(self, name, bit, read_only=False):
BitField.__init__(self, name, 1 << bit, adapter=None, bit_width=8, read_only=read_only)
class Device(object):
def __init__(self, i2c_address, i2c_dev=None, bit_width=8, registers=None):
self._bit_width = bit_width
self.locked = {}
self.registers = {}
self.values = {}
if type(i2c_address) is list:
self._i2c_addresses = i2c_address
self._i2c_address = i2c_address[0]
else:
self._i2c_addresses = [i2c_address]
self._i2c_address = i2c_address
self._i2c = i2c_dev
if self._i2c is None:
import smbus
self._i2c = smbus.SMBus(1)
for register in registers:
self.locked[register.name] = False
self.values[register.name] = 0
self.registers[register.name] = register
self.__dict__[register.name] = _RegisterProxy(self, register)
def lock_register(self, name):
self.locked[name] = True
def unlock_register(self, name):
self.locked[name] = False
def read_register(self, name):
register = self.registers[name]
if register.volatile or not register.is_read:
self.values[register.name] = self._i2c_read(register.address, register.bit_width)
register.is_read = True
return self.values[register.name]
def write_register(self, name):
register = self.registers[name]
return self._i2c_write(register.address, self.values[register.name], register.bit_width)
def get_addresses(self):
return self._i2c_addresses
def select_address(self, address):
if address in self._i2c_addresses:
self._i2c_address = address
return True
raise ValueError(“Address {:02x} invalid!”.format(address))
def next_address(self):
next_addr = self._i2c_addresses.index(self._i2c_address)
next_addr += 1
next_addr %= len(self._i2c_addresses)
self._i2c_address = self._i2c_addresses[next_addr]
return self._i2c_address
def set(self, register, **kwargs):
“””Write one or more fields on a device register.
Accepts multiple keyword arguments, one for each field to write.
:param register: Name of register to write.
“””
self.read_register(register)
self.lock_register(register)
for field in kwargs.keys():
value = kwargs.get(field)
self.set_field(register, field, value)
self.write_register(register)
self.unlock_register(register)
def get(self, register):
“””Get a namedtuple containing register fields.
:param register: Name of register to retrieve
“””
result = {}
self.read_register(register)
for field in self.registers[register].fields:
result[field] = self.get_field(register, field)
return self.registers[register].namedtuple(**result)
def get_field(self, register, field):
register = self.registers[register]
field = register.fields[field]
if not self.locked[register.name]:
self.read_register(register.name)
value = self.values[register.name]
value = (value & field.mask) >> _trailing_zeros(field.mask, register.bit_width)
if field.adapter is not None:
value = field.adapter._decode(value)
return value
def set_field(self, register, field, value):
register = self.registers[register]
field = register.fields[field]
shift = _trailing_zeros(field.mask, register.bit_width)
if field.adapter is not None:
value = field.adapter._encode(value)
if not self.locked[register.name]:
self.read_register(register.name)
reg_value = self.values[register.name]
reg_value &= ~field.mask
reg_value |= (value << shift) & field.mask
self.values[register.name] = reg_value
if not self.locked[register.name]:
self.write_register(register.name)
def get_register(self, register):
register = self.registers[register]
return self._i2c_read(register.address, register.bit_width)
def _i2c_write(self, register, value, bit_width):
values = _int_to_bytes(value, bit_width // self._bit_width, ‘big’)
values = list(values)
self._i2c.write_i2c_block_data(self._i2c_address, register, values)
def _i2c_read(self, register, bit_width):
value = 0
for x in self._i2c.read_i2c_block_data(self._i2c_address, register, bit_width // self._bit_width):
value <<= 8
value |= x
return value
pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/i2cdevice $
pyromori bmp280 driver (formatted) tlfong 2020may20hkt1501
# pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/bmp280 $ cat __init__.py
# “””BMP280 Driver.”””
from i2cdevice import Device, Register, BitField, _int_to_bytes
from i2cdevice.adapter import LookupAdapter, Adapter
import struct
__version__ = ‘0.0.3’
CHIP_ID = 0x58
I2C_ADDRESS_GND = 0x76
I2C_ADDRESS_VCC = 0x77
class S16Adapter(Adapter):
“””Convert unsigned 16bit integer to signed.”””
def _decode(self, value):
return struct.unpack(‘<h’, _int_to_bytes(value, 2))[0]
class U16Adapter(Adapter):
“””Convert from bytes to an unsigned 16bit integer.”””
def _decode(self, value):
return struct.unpack(‘<H’, _int_to_bytes(value, 2))[0]
class BMP280Calibration():
def __init__(self):
self.dig_t1 = 0
self.dig_t2 = 0
self.dig_t3 = 0
self.dig_p1 = 0
self.dig_p2 = 0
self.dig_p3 = 0
self.dig_p4 = 0
self.dig_p5 = 0
self.dig_p6 = 0
self.dig_p7 = 0
self.dig_p8 = 0
self.dig_p9 = 0
self.temperature_fine = 0
def set_from_namedtuple(self, value):
# Iterate through a tuple supplied by i2cdevice
# and copy its values into the class attributes
for key in self.__dict__.keys():
try:
setattr(self, key, getattr(value, key))
except AttributeError:
pass
def compensate_temperature(self, raw_temperature):
var1 = (raw_temperature / 16384.0 – self.dig_t1 / 1024.0) * self.dig_t2
var2 = raw_temperature / 131072.0 – self.dig_t1 / 8192.0
var2 = var2 * var2 * self.dig_t3
self.temperature_fine = (var1 + var2)
return self.temperature_fine / 5120.0
def compensate_pressure(self, raw_pressure):
var1 = self.temperature_fine / 2.0 – 64000.0
var2 = var1 * var1 * self.dig_p6 / 32768.0
var2 = var2 + var1 * self.dig_p5 * 2
var2 = var2 / 4.0 + self.dig_p4 * 65536.0
var1 = (self.dig_p3 * var1 * var1 / 524288.0 + self.dig_p2 * var1) / 524288.0
var1 = (1.0 + var1 / 32768.0) * self.dig_p1
pressure = 1048576.0 – raw_pressure
pressure = (pressure – var2 / 4096.0) * 6250.0 / var1
var1 = self.dig_p9 * pressure * pressure / 2147483648.0
var2 = pressure * self.dig_p8 / 32768.0
return pressure + (var1 + var2 + self.dig_p7) / 16.0
class BMP280:
def __init__(self, i2c_addr=I2C_ADDRESS_GND, i2c_dev=None):
self.calibration = BMP280Calibration()
self._is_setup = False
self._i2c_addr = i2c_addr
self._i2c_dev = i2c_dev
self._bmp280 = Device([I2C_ADDRESS_GND, I2C_ADDRESS_VCC], i2c_dev=self._i2c_dev, bit_width=8, registers=(
Register(‘CHIP_ID’, 0xD0, fields=(
BitField(‘id’, 0xFF),
)),
Register(‘RESET’, 0xE0, fields=(
BitField(‘reset’, 0xFF),
)),
Register(‘STATUS’, 0xF3, fields=(
BitField(‘measuring’, 0b00001000), # 1 when conversion is running
BitField(‘im_update’, 0b00000001), # 1 when NVM data is being copied
)),
Register(‘CTRL_MEAS’, 0xF4, fields=(
BitField(‘osrs_t’, 0b11100000, # Temperature oversampling
adapter=LookupAdapter({
1: 0b001,
2: 0b010,
4: 0b011,
8: 0b100,
16: 0b101
})),
BitField(‘osrs_p’, 0b00011100, # Pressure oversampling
adapter=LookupAdapter({
1: 0b001,
2: 0b010,
4: 0b011,
8: 0b100,
16: 0b101})),
BitField(‘mode’, 0b00000011, # Power mode
adapter=LookupAdapter({
‘sleep’: 0b00,
‘forced’: 0b10,
‘normal’: 0b11})),
)),
Register(‘CONFIG’, 0xF5, fields=(
BitField(‘t_sb’, 0b11100000, # Temp standby duration in normal mode
adapter=LookupAdapter({
0.5: 0b000,
62.5: 0b001,
125: 0b010,
250: 0b011,
500: 0b100,
1000: 0b101,
2000: 0b110,
4000: 0b111})),
BitField(‘filter’, 0b00011100), # Controls the time constant of the IIR filter
BitField(‘spi3w_en’, 0b0000001, read_only=True), # Enable 3-wire SPI interface when set to 1. IE: Don’t set this bit!
)),
Register(‘DATA’, 0xF7, fields=(
BitField(‘temperature’, 0x000000FFFFF0),
BitField(‘pressure’, 0xFFFFF0000000),
), bit_width=48),
Register(‘CALIBRATION’, 0x88, fields=(
BitField(‘dig_t1’, 0xFFFF << 16 * 11, adapter=U16Adapter()), # 0x88 0x89
BitField(‘dig_t2’, 0xFFFF << 16 * 10, adapter=S16Adapter()), # 0x8A 0x8B
BitField(‘dig_t3’, 0xFFFF << 16 * 9, adapter=S16Adapter()), # 0x8C 0x8D
BitField(‘dig_p1’, 0xFFFF << 16 * 8, adapter=U16Adapter()), # 0x8E 0x8F
BitField(‘dig_p2’, 0xFFFF << 16 * 7, adapter=S16Adapter()), # 0x90 0x91
BitField(‘dig_p3’, 0xFFFF << 16 * 6, adapter=S16Adapter()), # 0x92 0x93
BitField(‘dig_p4’, 0xFFFF << 16 * 5, adapter=S16Adapter()), # 0x94 0x95
BitField(‘dig_p5’, 0xFFFF << 16 * 4, adapter=S16Adapter()), # 0x96 0x97
BitField(‘dig_p6’, 0xFFFF << 16 * 3, adapter=S16Adapter()), # 0x98 0x99
BitField(‘dig_p7’, 0xFFFF << 16 * 2, adapter=S16Adapter()), # 0x9A 0x9B
BitField(‘dig_p8’, 0xFFFF << 16 * 1, adapter=S16Adapter()), # 0x9C 0x9D
BitField(‘dig_p9’, 0xFFFF << 16 * 0, adapter=S16Adapter()), # 0x9E 0x9F
), bit_width=192)
))
def setup(self):
if self._is_setup:
return
self._is_setup = True
self._bmp280.select_address(self._i2c_addr)
try:
chip = self._bmp280.get(‘CHIP_ID’)
if chip.id != CHIP_ID:
raise RuntimeError(“Unable to find bmp280 on 0x{:02x}, CHIP_ID returned {:02x}”.format(self._i2c_addr, chip.id))
except IOError:
raise RuntimeError(“Unable to find bmp280 on 0x{:02x}, IOError”.format(self._i2c_addr))
self._bmp280.set(‘CTRL_MEAS’,
mode=’normal’,
osrs_t=16,
osrs_p=16)
self._bmp280.set(‘CONFIG’,
t_sb=500,
filter=2)
self.calibration.set_from_namedtuple(self._bmp280.get(‘CALIBRATION’))
def update_sensor(self):
self.setup()
raw = self._bmp280.get(‘DATA’)
self.temperature = self.calibration.compensate_temperature(raw.temperature)
self.pressure = self.calibration.compensate_pressure(raw.pressure) / 100.0
def get_temperature(self):
self.update_sensor()
return self.temperature
def get_pressure(self):
self.update_sensor()
return self.pressure
def get_altitude(self, qnh=1013.25):
self.update_sensor()
pressure = self.get_pressure()
altitude = 44330.0 * (1.0 – pow(pressure / qnh, (1.0 / 5.255)))
return altitude
# pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/bmp280 $
pyromori bmp280 driver (raw) tlfong 2020may20hkt1501
# pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/bmp280 $ cat __init__.py
# “””BMP280 Driver.”””
from i2cdevice import Device, Register, BitField, _int_to_bytes
from i2cdevice.adapter import LookupAdapter, Adapter
import struct
__version__ = ‘0.0.3’
CHIP_ID = 0x58
I2C_ADDRESS_GND = 0x76
I2C_ADDRESS_VCC = 0x77
class S16Adapter(Adapter):
“””Convert unsigned 16bit integer to signed.”””
def _decode(self, value):
return struct.unpack(‘<h’, _int_to_bytes(value, 2))[0]
class U16Adapter(Adapter):
“””Convert from bytes to an unsigned 16bit integer.”””
def _decode(self, value):
return struct.unpack(‘<H’, _int_to_bytes(value, 2))[0]
class BMP280Calibration():
def __init__(self):
self.dig_t1 = 0
self.dig_t2 = 0
self.dig_t3 = 0
self.dig_p1 = 0
self.dig_p2 = 0
self.dig_p3 = 0
self.dig_p4 = 0
self.dig_p5 = 0
self.dig_p6 = 0
self.dig_p7 = 0
self.dig_p8 = 0
self.dig_p9 = 0
self.temperature_fine = 0
def set_from_namedtuple(self, value):
# Iterate through a tuple supplied by i2cdevice
# and copy its values into the class attributes
for key in self.__dict__.keys():
try:
setattr(self, key, getattr(value, key))
except AttributeError:
pass
def compensate_temperature(self, raw_temperature):
var1 = (raw_temperature / 16384.0 – self.dig_t1 / 1024.0) * self.dig_t2
var2 = raw_temperature / 131072.0 – self.dig_t1 / 8192.0
var2 = var2 * var2 * self.dig_t3
self.temperature_fine = (var1 + var2)
return self.temperature_fine / 5120.0
def compensate_pressure(self, raw_pressure):
var1 = self.temperature_fine / 2.0 – 64000.0
var2 = var1 * var1 * self.dig_p6 / 32768.0
var2 = var2 + var1 * self.dig_p5 * 2
var2 = var2 / 4.0 + self.dig_p4 * 65536.0
var1 = (self.dig_p3 * var1 * var1 / 524288.0 + self.dig_p2 * var1) / 524288.0
var1 = (1.0 + var1 / 32768.0) * self.dig_p1
pressure = 1048576.0 – raw_pressure
pressure = (pressure – var2 / 4096.0) * 6250.0 / var1
var1 = self.dig_p9 * pressure * pressure / 2147483648.0
var2 = pressure * self.dig_p8 / 32768.0
return pressure + (var1 + var2 + self.dig_p7) / 16.0
class BMP280:
def __init__(self, i2c_addr=I2C_ADDRESS_GND, i2c_dev=None):
self.calibration = BMP280Calibration()
self._is_setup = False
self._i2c_addr = i2c_addr
self._i2c_dev = i2c_dev
self._bmp280 = Device([I2C_ADDRESS_GND, I2C_ADDRESS_VCC], i2c_dev=self._i2c_dev, bit_width=8, registers=(
Register(‘CHIP_ID’, 0xD0, fields=(
BitField(‘id’, 0xFF),
)),
Register(‘RESET’, 0xE0, fields=(
BitField(‘reset’, 0xFF),
)),
Register(‘STATUS’, 0xF3, fields=(
BitField(‘measuring’, 0b00001000), # 1 when conversion is running
BitField(‘im_update’, 0b00000001), # 1 when NVM data is being copied
)),
Register(‘CTRL_MEAS’, 0xF4, fields=(
BitField(‘osrs_t’, 0b11100000, # Temperature oversampling
adapter=LookupAdapter({
1: 0b001,
2: 0b010,
4: 0b011,
8: 0b100,
16: 0b101
})),
BitField(‘osrs_p’, 0b00011100, # Pressure oversampling
adapter=LookupAdapter({
1: 0b001,
2: 0b010,
4: 0b011,
8: 0b100,
16: 0b101})),
BitField(‘mode’, 0b00000011, # Power mode
adapter=LookupAdapter({
‘sleep’: 0b00,
‘forced’: 0b10,
‘normal’: 0b11})),
)),
Register(‘CONFIG’, 0xF5, fields=(
BitField(‘t_sb’, 0b11100000, # Temp standby duration in normal mode
adapter=LookupAdapter({
0.5: 0b000,
62.5: 0b001,
125: 0b010,
250: 0b011,
500: 0b100,
1000: 0b101,
2000: 0b110,
4000: 0b111})),
BitField(‘filter’, 0b00011100), # Controls the time constant of the IIR filter
BitField(‘spi3w_en’, 0b0000001, read_only=True), # Enable 3-wire SPI interface when set to 1. IE: Don’t set this bit!
)),
Register(‘DATA’, 0xF7, fields=(
BitField(‘temperature’, 0x000000FFFFF0),
BitField(‘pressure’, 0xFFFFF0000000),
), bit_width=48),
Register(‘CALIBRATION’, 0x88, fields=(
BitField(‘dig_t1’, 0xFFFF << 16 * 11, adapter=U16Adapter()), # 0x88 0x89
BitField(‘dig_t2’, 0xFFFF << 16 * 10, adapter=S16Adapter()), # 0x8A 0x8B
BitField(‘dig_t3’, 0xFFFF << 16 * 9, adapter=S16Adapter()), # 0x8C 0x8D
BitField(‘dig_p1’, 0xFFFF << 16 * 8, adapter=U16Adapter()), # 0x8E 0x8F
BitField(‘dig_p2’, 0xFFFF << 16 * 7, adapter=S16Adapter()), # 0x90 0x91
BitField(‘dig_p3’, 0xFFFF << 16 * 6, adapter=S16Adapter()), # 0x92 0x93
BitField(‘dig_p4’, 0xFFFF << 16 * 5, adapter=S16Adapter()), # 0x94 0x95
BitField(‘dig_p5’, 0xFFFF << 16 * 4, adapter=S16Adapter()), # 0x96 0x97
BitField(‘dig_p6’, 0xFFFF << 16 * 3, adapter=S16Adapter()), # 0x98 0x99
BitField(‘dig_p7’, 0xFFFF << 16 * 2, adapter=S16Adapter()), # 0x9A 0x9B
BitField(‘dig_p8’, 0xFFFF << 16 * 1, adapter=S16Adapter()), # 0x9C 0x9D
BitField(‘dig_p9’, 0xFFFF << 16 * 0, adapter=S16Adapter()), # 0x9E 0x9F
), bit_width=192)
))
def setup(self):
if self._is_setup:
return
self._is_setup = True
self._bmp280.select_address(self._i2c_addr)
try:
chip = self._bmp280.get(‘CHIP_ID’)
if chip.id != CHIP_ID:
raise RuntimeError(“Unable to find bmp280 on 0x{:02x}, CHIP_ID returned {:02x}”.format(self._i2c_addr, chip.id))
except IOError:
raise RuntimeError(“Unable to find bmp280 on 0x{:02x}, IOError”.format(self._i2c_addr))
self._bmp280.set(‘CTRL_MEAS’,
mode=’normal’,
osrs_t=16,
osrs_p=16)
self._bmp280.set(‘CONFIG’,
t_sb=500,
filter=2)
self.calibration.set_from_namedtuple(self._bmp280.get(‘CALIBRATION’))
def update_sensor(self):
self.setup()
raw = self._bmp280.get(‘DATA’)
self.temperature = self.calibration.compensate_temperature(raw.temperature)
self.pressure = self.calibration.compensate_pressure(raw.pressure) / 100.0
def get_temperature(self):
self.update_sensor()
return self.temperature
def get_pressure(self):
self.update_sensor()
return self.pressure
def get_altitude(self, qnh=1013.25):
self.update_sensor()
pressure = self.get_pressure()
altitude = 44330.0 * (1.0 – pow(pressure / qnh, (1.0 / 5.255)))
return altitude
# pi@raspberrypi:/usr/local/lib/python3.7/dist-packages/bmp280 $
.END
Categories: Uncategorized