SITL: add Maxell SMBus battery support
This commit is contained in:
parent
f2af2b3458
commit
602a9592ce
@ -95,6 +95,7 @@ void SITL_State::_sitl_setup(const char *home_str)
|
|||||||
sitl_model->set_gripper_epm(&_sitl->gripper_epm_sim);
|
sitl_model->set_gripper_epm(&_sitl->gripper_epm_sim);
|
||||||
sitl_model->set_parachute(&_sitl->parachute_sim);
|
sitl_model->set_parachute(&_sitl->parachute_sim);
|
||||||
sitl_model->set_precland(&_sitl->precland_sim);
|
sitl_model->set_precland(&_sitl->precland_sim);
|
||||||
|
_sitl->i2c_sim.init();
|
||||||
sitl_model->set_i2c(&_sitl->i2c_sim);
|
sitl_model->set_i2c(&_sitl->i2c_sim);
|
||||||
|
|
||||||
if (_use_fg_view) {
|
if (_use_fg_view) {
|
||||||
|
76
libraries/SITL/SIM_BattMonitor_SMBus.cpp
Normal file
76
libraries/SITL/SIM_BattMonitor_SMBus.cpp
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#include "SIM_BattMonitor_SMBus.h"
|
||||||
|
|
||||||
|
#include <AP_Stats/AP_Stats.h>
|
||||||
|
|
||||||
|
SITL::SIM_BattMonitor_SMBus::SIM_BattMonitor_SMBus() :
|
||||||
|
I2CRegisters_16Bit()
|
||||||
|
{
|
||||||
|
add_register("Temperature", SMBusBattDevReg::TEMP, O_RDONLY);
|
||||||
|
add_register("Voltage", SMBusBattDevReg::VOLTAGE, O_RDONLY);
|
||||||
|
add_register("Current", SMBusBattDevReg::CURRENT, O_RDONLY);
|
||||||
|
add_register("Remaining Capacity", SMBusBattDevReg::REMAINING_CAPACITY, O_RDONLY);
|
||||||
|
add_register("Full Charge Capacity", SMBusBattDevReg::FULL_CHARGE_CAPACITY, O_RDONLY);
|
||||||
|
add_register("Cycle_Count", SMBusBattDevReg::CYCLE_COUNT, O_RDONLY);
|
||||||
|
add_register("Specification Info", SMBusBattDevReg::SPECIFICATION_INFO, O_RDONLY);
|
||||||
|
add_register("Serial", SMBusBattDevReg::SERIAL, O_RDONLY);
|
||||||
|
add_register("Manufacture Name", SMBusBattDevReg::MANUFACTURE_NAME, O_RDONLY);
|
||||||
|
add_register("Manufacture Data", SMBusBattDevReg::MANUFACTURE_DATA, O_RDONLY);
|
||||||
|
|
||||||
|
set_register(SMBusBattDevReg::TEMP, (int16_t)(15 + 273.15));
|
||||||
|
// see update for voltage
|
||||||
|
// see update for current
|
||||||
|
// TODO: remaining capacity
|
||||||
|
// TODO: full capacity
|
||||||
|
set_register(SMBusBattDevReg::CYCLE_COUNT, (uint16_t(39U)));
|
||||||
|
|
||||||
|
// Set SPECIFICATION_INFO
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint8_t revision : 4;
|
||||||
|
uint8_t version: 4;
|
||||||
|
uint8_t vscale: 4;
|
||||||
|
uint8_t ipscale: 4;
|
||||||
|
} fields;
|
||||||
|
uint16_t word;
|
||||||
|
} specinfo;
|
||||||
|
|
||||||
|
specinfo.fields.revision = 0b0001; // version 1
|
||||||
|
// specinfo.fields.version = 0b0011; // 1.1 with PEC; TODO!
|
||||||
|
specinfo.fields.version = 0b0001; // 1.0
|
||||||
|
specinfo.fields.vscale = 0b0000; // unknown...
|
||||||
|
specinfo.fields.ipscale = 0b0000; // unknown...
|
||||||
|
set_register(SMBusBattDevReg::SPECIFICATION_INFO, specinfo.word);
|
||||||
|
|
||||||
|
set_register(SMBusBattDevReg::SERIAL, (uint16_t)12345);
|
||||||
|
|
||||||
|
// Set MANUFACTURE_NAME
|
||||||
|
const char *manufacturer_name = "ArduPilot";
|
||||||
|
set_register(SMBusBattDevReg::MANUFACTURE_NAME, uint16_t(manufacturer_name[0]<<8|strlen(manufacturer_name)));
|
||||||
|
uint8_t i = 1; // already sent the first byte out....
|
||||||
|
while (i < strlen(manufacturer_name)) {
|
||||||
|
const uint8_t a = manufacturer_name[i++];
|
||||||
|
uint8_t b = 0;
|
||||||
|
if (i < strlen(manufacturer_name)) {
|
||||||
|
b = manufacturer_name[i];
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
const uint16_t value = b<<8 | a;
|
||||||
|
add_register("Name", SMBusBattDevReg::MANUFACTURE_NAME + i/2, O_RDONLY);
|
||||||
|
set_register(SMBusBattDevReg::MANUFACTURE_NAME + i/2, value);
|
||||||
|
}
|
||||||
|
// TODO: manufacturer data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SITL::SIM_BattMonitor_SMBus::update(const class Aircraft &aircraft)
|
||||||
|
{
|
||||||
|
const uint32_t now = AP_HAL::millis();
|
||||||
|
if (now - last_update_ms > 100) {
|
||||||
|
const float millivolts = AP::sitl()->state.battery_voltage * 1000.0f;
|
||||||
|
set_register(SMBusBattDevReg::VOLTAGE, uint16_t(millivolts));
|
||||||
|
// FIXME: is this REALLY what the hardware will do?
|
||||||
|
const int16_t current = constrain_int32(AP::sitl()->state.battery_current*-1000, -32768, 32767);
|
||||||
|
set_register(SMBusBattDevReg::CURRENT, current);
|
||||||
|
last_update_ms = now;
|
||||||
|
}
|
||||||
|
}
|
36
libraries/SITL/SIM_BattMonitor_SMBus.h
Normal file
36
libraries/SITL/SIM_BattMonitor_SMBus.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "SIM_I2CDevice.h"
|
||||||
|
|
||||||
|
namespace SITL {
|
||||||
|
|
||||||
|
class SMBusBattDevReg : public I2CRegEnum {
|
||||||
|
public:
|
||||||
|
static const uint8_t TEMP = 0x08; // Temperature
|
||||||
|
static const uint8_t VOLTAGE = 0x09; // Voltage
|
||||||
|
static const uint8_t CURRENT = 0x0A; // Current
|
||||||
|
static const uint8_t REMAINING_CAPACITY = 0x0F; // Remaining Capacity
|
||||||
|
static const uint8_t FULL_CHARGE_CAPACITY = 0x10; // Full Charge Capacity
|
||||||
|
static const uint8_t CYCLE_COUNT = 0x17; // Cycle Count
|
||||||
|
static const uint8_t SPECIFICATION_INFO = 0x1A; // Specification Info
|
||||||
|
static const uint8_t SERIAL = 0x1C; // Serial Number
|
||||||
|
static const uint8_t MANUFACTURE_NAME = 0x20; // Manufacture Name
|
||||||
|
static const uint8_t MANUFACTURE_DATA = 0x23; // Manufacture Data
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIM_BattMonitor_SMBus : public I2CDevice, protected I2CRegisters_16Bit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SIM_BattMonitor_SMBus();
|
||||||
|
|
||||||
|
virtual void update(const class Aircraft &aircraft) override;
|
||||||
|
|
||||||
|
int rdwr(I2C::i2c_rdwr_ioctl_data *&data) override {
|
||||||
|
return I2CRegisters_16Bit::rdwr(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint32_t last_update_ms;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SITL
|
61
libraries/SITL/SIM_BattMonitor_SMBus_Generic.cpp
Normal file
61
libraries/SITL/SIM_BattMonitor_SMBus_Generic.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#include "SIM_BattMonitor_SMBus_Generic.h"
|
||||||
|
|
||||||
|
SITL::SIM_BattMonitor_SMBus_Generic::SIM_BattMonitor_SMBus_Generic() :
|
||||||
|
SIM_BattMonitor_SMBus()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SITL::SIM_BattMonitor_SMBus_Generic::init()
|
||||||
|
{
|
||||||
|
switch (cellcount()) {
|
||||||
|
case 12:
|
||||||
|
add_register("Cell12", SMBusBattGenericDevReg::CELL12, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 11:
|
||||||
|
add_register("Cell11", SMBusBattGenericDevReg::CELL11, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 10:
|
||||||
|
add_register("Cell10", SMBusBattGenericDevReg::CELL10, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 9:
|
||||||
|
add_register("Cell9", SMBusBattGenericDevReg::CELL9, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 8:
|
||||||
|
add_register("Cell8", SMBusBattGenericDevReg::CELL8, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 7:
|
||||||
|
add_register("Cell7", SMBusBattGenericDevReg::CELL7, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 6:
|
||||||
|
add_register("Cell6", SMBusBattGenericDevReg::CELL6, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 5:
|
||||||
|
add_register("Cell5", SMBusBattGenericDevReg::CELL5, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 4:
|
||||||
|
add_register("Cell4", SMBusBattGenericDevReg::CELL4, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 3:
|
||||||
|
add_register("Cell3", SMBusBattGenericDevReg::CELL3, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 2:
|
||||||
|
add_register("Cell2", SMBusBattGenericDevReg::CELL2, O_RDONLY);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case 1:
|
||||||
|
add_register("Cell1", SMBusBattGenericDevReg::CELL1, O_RDONLY);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
AP_HAL::panic("Bad cellcount %u", cellcount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SITL::SIM_BattMonitor_SMBus_Generic::update(const class Aircraft &aircraft)
|
||||||
|
{
|
||||||
|
SIM_BattMonitor_SMBus::update(aircraft);
|
||||||
|
|
||||||
|
// pretend to have three cells connected
|
||||||
|
const float millivolts = AP::sitl()->state.battery_voltage * 1000.0f;
|
||||||
|
set_register(SMBusBattGenericDevReg::CELL1, uint16_t(millivolts/3.0f - 100.0f));
|
||||||
|
set_register(SMBusBattGenericDevReg::CELL2, uint16_t(millivolts/3.0f));
|
||||||
|
set_register(SMBusBattGenericDevReg::CELL3, uint16_t(millivolts/3.0f + 100.0f));
|
||||||
|
}
|
33
libraries/SITL/SIM_BattMonitor_SMBus_Generic.h
Normal file
33
libraries/SITL/SIM_BattMonitor_SMBus_Generic.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "SIM_BattMonitor_SMBus.h"
|
||||||
|
|
||||||
|
namespace SITL {
|
||||||
|
|
||||||
|
class SMBusBattGenericDevReg : public SMBusBattDevReg {
|
||||||
|
public:
|
||||||
|
static const uint8_t CELL1 = 0x3f;
|
||||||
|
static const uint8_t CELL2 = 0x3e;
|
||||||
|
static const uint8_t CELL3 = 0x3d;
|
||||||
|
static const uint8_t CELL4 = 0x3c;
|
||||||
|
static const uint8_t CELL5 = 0x3b;
|
||||||
|
static const uint8_t CELL6 = 0x3a;
|
||||||
|
static const uint8_t CELL7 = 0x39;
|
||||||
|
static const uint8_t CELL8 = 0x38;
|
||||||
|
static const uint8_t CELL9 = 0x37;
|
||||||
|
static const uint8_t CELL10 = 0x36;
|
||||||
|
static const uint8_t CELL11 = 0x35;
|
||||||
|
static const uint8_t CELL12 = 0x34;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIM_BattMonitor_SMBus_Generic : public SIM_BattMonitor_SMBus
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SIM_BattMonitor_SMBus_Generic();
|
||||||
|
void init() override;
|
||||||
|
void update(const class Aircraft &aircraft) override;
|
||||||
|
|
||||||
|
virtual uint8_t cellcount() const = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SITL
|
7
libraries/SITL/SIM_BattMonitor_SMBus_Maxell.cpp
Normal file
7
libraries/SITL/SIM_BattMonitor_SMBus_Maxell.cpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include "SIM_BattMonitor_SMBus_Maxell.h"
|
||||||
|
|
||||||
|
SITL::Maxell::Maxell() :
|
||||||
|
SIM_BattMonitor_SMBus_Generic()
|
||||||
|
{
|
||||||
|
set_register(SMBusBattGenericDevReg::SERIAL, (uint16_t)37);
|
||||||
|
}
|
26
libraries/SITL/SIM_BattMonitor_SMBus_Maxell.h
Normal file
26
libraries/SITL/SIM_BattMonitor_SMBus_Maxell.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include "SIM_BattMonitor_SMBus_Generic.h"
|
||||||
|
|
||||||
|
#include <AP_Common/Bitmask.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Testing:
|
||||||
|
|
||||||
|
param set BATT_MONITOR 16
|
||||||
|
reboot
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SITL {
|
||||||
|
|
||||||
|
class Maxell : public SIM_BattMonitor_SMBus_Generic
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
Maxell();
|
||||||
|
|
||||||
|
uint8_t cellcount() const override { return 3; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SITL
|
@ -23,6 +23,7 @@
|
|||||||
#include "SIM_I2C.h"
|
#include "SIM_I2C.h"
|
||||||
#include "SIM_ToshibaLED.h"
|
#include "SIM_ToshibaLED.h"
|
||||||
#include "SIM_MaxSonarI2CXL.h"
|
#include "SIM_MaxSonarI2CXL.h"
|
||||||
|
#include "SIM_BattMonitor_SMBus_Maxell.h"
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ static IgnoredI2CDevice ignored;
|
|||||||
|
|
||||||
static ToshibaLED toshibaled;
|
static ToshibaLED toshibaled;
|
||||||
static MaxSonarI2CXL maxsonari2cxl;
|
static MaxSonarI2CXL maxsonari2cxl;
|
||||||
|
static Maxell maxell;
|
||||||
|
|
||||||
struct i2c_device_at_address {
|
struct i2c_device_at_address {
|
||||||
uint8_t bus;
|
uint8_t bus;
|
||||||
@ -55,8 +57,16 @@ struct i2c_device_at_address {
|
|||||||
{ 1, 0x40, ignored }, // KellerLD
|
{ 1, 0x40, ignored }, // KellerLD
|
||||||
{ 1, 0x70, maxsonari2cxl },
|
{ 1, 0x70, maxsonari2cxl },
|
||||||
{ 1, 0x76, ignored }, // MS56XX
|
{ 1, 0x76, ignored }, // MS56XX
|
||||||
|
{ 2, 0x0B, maxell },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void I2C::init()
|
||||||
|
{
|
||||||
|
for (auto &i : i2c_devices) {
|
||||||
|
i.device.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void I2C::update(const class Aircraft &aircraft)
|
void I2C::update(const class Aircraft &aircraft)
|
||||||
{
|
{
|
||||||
for (auto daa : i2c_devices) {
|
for (auto daa : i2c_devices) {
|
||||||
|
@ -29,6 +29,8 @@ class I2C {
|
|||||||
public:
|
public:
|
||||||
I2C() {}
|
I2C() {}
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
// update i2c state
|
// update i2c state
|
||||||
void update(const class Aircraft &aircraft);
|
void update(const class Aircraft &aircraft);
|
||||||
|
|
||||||
|
76
libraries/SITL/SIM_I2CDevice.cpp
Normal file
76
libraries/SITL/SIM_I2CDevice.cpp
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#include "SIM_I2CDevice.h"
|
||||||
|
|
||||||
|
void SITL::I2CRegisters::add_register(const char *name, uint8_t reg, int8_t mode)
|
||||||
|
{
|
||||||
|
// ::fprintf(stderr, "Adding register %u (0x%02x) (%s)\n", reg, reg, name);
|
||||||
|
regname[reg] = name;
|
||||||
|
if (mode == O_RDONLY || mode == O_RDWR) {
|
||||||
|
readable_registers.set((uint8_t)reg);
|
||||||
|
}
|
||||||
|
if (mode == O_WRONLY || mode == O_RDWR) {
|
||||||
|
writable_registers.set((uint8_t)reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SITL::I2CRegisters_16Bit::set_register(uint8_t reg, uint16_t value)
|
||||||
|
{
|
||||||
|
if (regname[reg] == nullptr) {
|
||||||
|
AP_HAL::panic("Setting un-named register %u", reg);
|
||||||
|
}
|
||||||
|
// ::fprintf(stderr, "Setting %u (0x%02x) (%s) to 0x%02x (%c)\n", (unsigned)reg, (unsigned)reg, regname[reg], (unsigned)value, value);
|
||||||
|
word[reg] = htobe16(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SITL::I2CRegisters_16Bit::set_register(uint8_t reg, int16_t value)
|
||||||
|
{
|
||||||
|
if (regname[reg] == nullptr) {
|
||||||
|
AP_HAL::panic("Setting un-named register %u", reg);
|
||||||
|
}
|
||||||
|
// ::fprintf(stderr, "Setting %s (%u) to 0x%02x (%c)\n", regname[reg], (unsigned)reg, (signed)value, value);
|
||||||
|
word[reg] = htobe16(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SITL::I2CRegisters_16Bit::rdwr(I2C::i2c_rdwr_ioctl_data *&data)
|
||||||
|
{
|
||||||
|
if (data->nmsgs == 2) {
|
||||||
|
// data read request
|
||||||
|
if (data->msgs[0].flags != 0) {
|
||||||
|
AP_HAL::panic("Unexpected flags");
|
||||||
|
}
|
||||||
|
if (data->msgs[1].flags != I2C_M_RD) {
|
||||||
|
AP_HAL::panic("Unexpected flags");
|
||||||
|
}
|
||||||
|
const uint8_t reg_base_addr = data->msgs[0].buf[0];
|
||||||
|
uint8_t bytes_copied = 0;
|
||||||
|
while (bytes_copied < data->msgs[1].len) {
|
||||||
|
const uint8_t reg_addr = reg_base_addr + bytes_copied/2;
|
||||||
|
if (!readable_registers.get(reg_addr)) {
|
||||||
|
// ::printf("Register 0x%02x is not readable!\n", reg_addr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const uint16_t register_value = word[reg_addr];
|
||||||
|
data->msgs[1].buf[bytes_copied++] = register_value >> 8;
|
||||||
|
if (bytes_copied < data->msgs[1].len) {
|
||||||
|
data->msgs[1].buf[bytes_copied++] = register_value & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->msgs[1].len = bytes_copied;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->nmsgs == 1) {
|
||||||
|
// data write request
|
||||||
|
if (data->msgs[0].flags != 0) {
|
||||||
|
AP_HAL::panic("Unexpected flags");
|
||||||
|
}
|
||||||
|
const uint8_t reg_addr = data->msgs[0].buf[0];
|
||||||
|
if (!writable_registers.get(reg_addr)) {
|
||||||
|
AP_HAL::panic("Register 0x%02x is not writable!", reg_addr);
|
||||||
|
}
|
||||||
|
const uint16_t register_value = data->msgs[0].buf[2] << 8 | data->msgs[0].buf[1];
|
||||||
|
word[reg_addr] = register_value;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
};
|
@ -6,8 +6,40 @@
|
|||||||
|
|
||||||
namespace SITL {
|
namespace SITL {
|
||||||
|
|
||||||
|
class I2CRegEnum {
|
||||||
|
// a class to hold register addresses as an enumeration
|
||||||
|
};
|
||||||
|
|
||||||
|
class I2CRegisters {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual int rdwr(I2C::i2c_rdwr_ioctl_data *&data) = 0;
|
||||||
|
|
||||||
|
void add_register(const char *name, uint8_t reg, int8_t mode);
|
||||||
|
|
||||||
|
const char *regname[256];
|
||||||
|
Bitmask<256> writable_registers;
|
||||||
|
Bitmask<256> readable_registers;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class I2CRegisters_16Bit : public I2CRegisters {
|
||||||
|
public:
|
||||||
|
int rdwr(I2C::i2c_rdwr_ioctl_data *&data) override;
|
||||||
|
void set_register(uint8_t reg, uint16_t value);
|
||||||
|
void set_register(uint8_t reg, int16_t value);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
uint16_t word[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class I2CDevice {
|
class I2CDevice {
|
||||||
public:
|
public:
|
||||||
|
virtual void init() {}
|
||||||
|
|
||||||
virtual void update(const class Aircraft &aircraft) { }
|
virtual void update(const class Aircraft &aircraft) { }
|
||||||
|
|
||||||
virtual int rdwr(I2C::i2c_rdwr_ioctl_data *&data) = 0;
|
virtual int rdwr(I2C::i2c_rdwr_ioctl_data *&data) = 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user