Raspberry Pi не декодирует данные в кодировке Base 64, отправленные через BLE с использованием службы Nordic UART из приPython

Программы на Python
Ответить Пред. темаСлед. тема
Anonymous
 Raspberry Pi не декодирует данные в кодировке Base 64, отправленные через BLE с использованием службы Nordic UART из при

Сообщение Anonymous »

Я использую собственное приложение React для записи и чтения файла .ini на Raspberry Pi, который я называю концентратором, на котором работает сервер GATT с использованием службы Nordic UART (NUS).
Я могу подключиться к RPi и отправить строку «retriveHubConfig», которая должна считывать текущее содержимое файла .ini и отображать его в приложении. Я также отправляю данные JSON, которые можно автоматически записать в файл .ini, без необходимости ввода их пользователем приложения. Остальная часть файла .ini заполняется данными, которые вводит пользователь, и записывается в RPi так же, как и раньше.
Raspberry Pi получает данные в кодировке Base 64 и распечатывает их. до и после декодирования.
Однако процесс декодирования, похоже, не работает. Он распечатывает массив байтов данных JSON и строку «retrieveHubConfig» фрагментами длиной до 100 символов. Также есть символ окончания передачи, отправляемый после отправки полных данных JSON, и еще один для «retriveHubConfig». Ветка исключений блока try исключений на сервере GATT печатает:
(первые 100 символов данных JSON)
Ошибка в WriteValue: неправильное заполнение
(символы 101–200 данных JSON)
Ошибка в WriteValue: кодек 'utf-8' не может декодировать байт 0xdb в позиции 0 : неверный байт продолжения
(последние 12 символов данных JSON)
Ошибка в WriteValue: кодек 'utf-8' не может декодировать байт 0xdf в позиции 0: неверный байт продолжения.
(все 17 символов извлекаемого концентратора)
Ошибка в WriteValue: неверная строка в кодировке Base64: количество символов данных. (17) не может быть на 1 больше, чем кратное 4.
Интересно, что ему удается выполнить оператор печати «Полученные декодированные данные base64:» для конца передаваемых символов, но он остается пустым. строка.
Я получаю тот же результат от Pi, если использую:
constpaddedData = command.padEnd(Math.ceil(command.length) / 4) * 4, "=");
чтобы попытаться решить проблему с неправильным заполнением.
Я пробовал использовать буфер и библиотеки реагирования-native-base64. Они оба производят один и тот же результат от Pi. Я ожидаю увидеть данные JSON:
{"hub_settings": {"hello_timer": "3000", "inter-master_multicast_address": "244.0.0.221", "neighbour_inter-master_multicast_address" : "244.0.0.222"}}
и: "retriveHubConfig"
в операторе печати "Полученные декодированные данные base64:".
Вот выходные данные сервера Raspberry Pi GATT:
Полученные данные: dbus.Array([dbus.Byte(123), dbus.Byte(34) , dbus.Byte(104), dbus.Byte(117), dbus.Byte(98), dbus.Byte(83), dbus.Byte(101), dbus.Byte(116), dbus.Byte(116), dbus.Byte(105), dbus.Byte(110), dbus.Byte(103), dbus.Byte(115), dbus.Byte(34), dbus.Byte(58), dbus.Byte(123), dbus .Byte(34), dbus.Byte(104), dbus.Byte(117), dbus.Byte(98), dbus.Byte(95), dbus.Byte(115), dbus.Byte(101), dbus. Байт(116), dbus.Byte(116), dbus.Byte(105), dbus.Byte(110), dbus.Byte(103), dbus.Byte(115), dbus.Byte(34), dbus.Byte (58), dbus.Byte(123), dbus.Byte(34), dbus.Byte(105), dbus.Byte(110), dbus.Byte(116), dbus.Byte(101), dbus.Byte( 114), dbus.Byte(45), dbus.Byte(109), dbus.Byte(97), dbus.Byte(115), dbus.Byte(116), dbus.Byte(101), dbus.Byte(114) ), dbus.Byte(95), dbus.Byte(109), dbus.Byte(117), dbus.Byte(108), dbus.Byte(116), dbus.Byte(105), dbus.Byte(99) , dbus.Byte(97), dbus.Byte(115), dbus.Byte(116), dbus.Byte(95), dbus.Byte(97), dbus.Byte(100), dbus.Byte(100), dbus.Byte(114), dbus.Byte(101), dbus.Byte(115), dbus.Byte(115), dbus.Byte(34), dbus.Byte(58), dbus.Byte(34), dbus .Byte(50), dbus.Byte(52), dbus.Byte(52), dbus.Byte(46), dbus.Byte(48), dbus.Byte(46), dbus.Byte(48), dbus. Byte(46), dbus.Byte(50)], подпись=dbus.Signature('y'))
Ошибка в WriteValue: неправильное заполнение
Полученные данные: dbus.Array([dbus.Byte( 50), dbus.Byte(49), dbus.Byte(34), dbus.Byte(44), dbus.Byte(34), dbus.Byte(110), dbus.Byte(101), dbus.Byte(105) ), dbus.Byte(103), dbus.Byte(104), dbus.Byte(98), dbus.Byte(111), dbus.Byte(117), dbus.Byte(114), dbus.Byte(95) , dbus.Byte(105), dbus.Byte(110), dbus.Byte(116), dbus.Byte(101), dbus.Byte(114), dbus.Byte(45), dbus.Byte(109), dbus.Byte(97), dbus.Byte(115), dbus.Byte(116), dbus.Byte(101), dbus.Byte(114), dbus.Byte(95), dbus.Byte(109), dbus .Byte(117), dbus.Byte(108), dbus.Byte(116), dbus.Byte(105), dbus.Byte(99), dbus.Byte(97), dbus.Byte(115), dbus. Байт(116), dbus.Byte(95), dbus.Byte(97), dbus.Byte(100), dbus.Byte(100), dbus.Byte(114), dbus.Byte(101), dbus.Byte (115), dbus.Byte(115), dbus.Byte(34), dbus.Byte(58), dbus.Byte(34), dbus.Byte(50), dbus.Byte(52), dbus.Byte( 52), dbus.Byte(46), dbus.Byte(48), dbus.Byte(46), dbus.Byte(48), dbus.Byte(46), dbus.Byte(50), dbus.Byte(50) ), dbus.Byte(50), dbus.Byte(34), dbus.Byte(44), dbus.Byte(34), dbus.Byte(104), dbus.Byte(101), dbus.Byte(108) , dbus.Byte(108), dbus.Byte(111), dbus.Byte(95), dbus.Byte(116), dbus.Byte(105), dbus.Byte(109), dbus.Byte(101), dbus.Byte(114), dbus.Byte(34), dbus.Byte(58)], подпись=dbus.Signature('y'))
Ошибка в WriteValue: кодек 'utf-8' не может декодировать байт 0xdb в позиции 0: неверный байт продолжения
Полученные данные: dbus.Array([dbus.Byte(34), dbus.Byte(51), dbus.Byte(48), dbus.Byte(48), dbus.Byte(48), dbus.Byte(34), dbus.Byte(125), dbus.Byte(125), dbus.Byte(125)], подпись=dbus.Signature('y'))
Ошибка в WriteValue: кодек 'utf-8' не может декодировать байт 0xdf в позиции 0: неверный байт продолжения
Полученные данные: dbus.Array([dbus.Byte(4)], подпись=dbus.Signature( 'y'))
Полученные декодированные данные base64:

Полученные данные: dbus.Array([dbus.Byte(114), dbus.Byte(101), dbus.Byte(116) , dbus.Byte(114), dbus.Byte(105), dbus.Byte(101), dbus.Byte(118), dbus.Byte(101), dbus.Byte(72), dbus.Byte(117), dbus.Byte(98), dbus.Byte(67), dbus.Byte(111), dbus.Byte(110), dbus.Byte(102), dbus.Byte(105), dbus.Byte(103)], Signature=dbus.Signature('y'))
Ошибка в WriteValue: неверная строка в кодировке Base64: количество символов данных (17) не может быть на 1 больше, чем кратное 4.
Полученные данные: dbus. Array([dbus.Byte(4)], Signature=dbus.Signature('y'))
Полученные декодированные данные base64:
Нативный код реакции. Метод getHubConfig() содержит кодирование и запись в Raspberry Pi через операторы BLE:
import base64 from "react-native-base64";
import { Context } from "../Auth/AuthContext.js";

// Method that presents the user with a list of BLE hubs or watches to choose from and allows
// them to connect to and transfer data between this app and the RPi.

const BleScanner = ({ engineerMode, deviceType }) => {
const globalContext = useContext(Context);
const { selectedDevice, setSelectedDevice } = globalContext;

// UUIDs for the UART service and characteristics. These are used to send the commands to the RPi.
// The RPi will respond with the result of the command.They are standard Nordic UART UUIDs.
const UART_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const UART_RX_CHARACTERISTIC_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
const UART_TX_CHARACTERISTIC_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";

// Set the MTU to the desired maximum value
const MTU = 100;
//End - of - Transmission marker, ASCII code for EOT
const EOT_MARKER = "\x04";

const [devices, setDevices] = useState([]);
const [isConnecting, setIsConnecting] = useState(false);
// New state to track if the app is connecting to the RPi or watch
const [showParameters, setShowParameters] = useState(false);
// New state to track if the parameters of the selected hub or watch are visible

const getHubConfig = async () => {
setShowParameters(false);
setIsConnecting(true);

const hubSettings = {"hub_settings": {"hello_timer": "3000", "inter-master_multicast_address": "244.0.0.221", "neighbour_inter-master_multicast_address": "244.0.0.222"}}

const commands = [
JSON.stringify({ hubSettings }),
"retrieveHubConfig",
];

try {
// Check if RPi is still connected to the device or not. If it is not connected, then connect to the device.
let connectedDevice = await handleDeviceConnection(selectedDevice);

const service = await connectedDevice.discoverAllServicesAndCharacteristics();
const characteristics = await service.characteristicsForService(UART_SERVICE_UUID);
const rx_characteristic = characteristics.find((c) => c.uuid === UART_RX_CHARACTERISTIC_UUID);

for (const command of commands) {
let data = null;
console.log("Command: ", command);
if (command === "retrieveHubConfig") {
// const paddedData = command.padEnd(Math.ceil(command.length / 4) * 4, "=");
data = base64.encode(command);
} else {
// const paddedData = command.padEnd(Math.ceil(command.length / 4) * 4, "=");
data = base64.encode(command);
}
console.log("Data to send: ", data);
console.log("Decoded data to send: ", Buffer.from(data, "base64").toString("ascii"));

// Calculate the number of packets needed to send the data
const numPackets = Math.ceil(data.length / MTU);
console.log("Number of packets: ", numPackets); // Log the number of packets
// Send the data in smaller units
for (let i = 0; i < numPackets; i++) {
const start = i * MTU;
const end = Math.min(start + MTU, data.length);
const packetData = data.slice(start, end);
console.log("Packet data: ", packetData);
console.log("Packet data length: ", packetData.length);

// Send the packet to the connected device
await rx_characteristic.writeWithoutResponse(packetData);

console.log(`Packet ${i + 1}/${numPackets} sent.`);

// Pause for 0.5 seconds between packets.
await new Promise(resolve => setTimeout(resolve, 1000));

if (i == numPackets - 1) {
await rx_characteristic.writeWithoutResponse(Buffer.from(EOT_MARKER).toString("base64"));
console.log("EOT length: ", Buffer.from(EOT_MARKER).toString("base64").length);
}
}

console.log("Data transmission complete.");
}
saveExtracedData(extractKeyValuePairs(readCharacteristic(connectedDevice)));
setShowParameters(true);
setIsConnecting(false);
} catch (error) {
// If an error occurs, log the error and set the isConnecting state variable to false
console.log("Failed to send commands: ", error);
console.log("Error code: ", error.errorCode);

setIsConnecting(false);
Alert.alert("Couldn't connect.",
Platform.OS === "ios" ?
"Check your device is paired with Tycho hub in your device's bluetooth settings. Stay close to Tycho hub." : "Stay close to Tycho hub & retry.");
}
};

const readCharacteristic = (device) => {
try {
let isEOTReceived = false;
let fullData = "";

while (!isEOTReceived) {
console.log("Reading characteristic...");
const characteristic = manager.readCharacteristicForDevice(
device.id,
UART_SERVICE_UUID,
UART_TX_CHARACTERISTIC_UUID
);
console.log("Characteristic: ", characteristic);
const data = characteristic.value;
console.log("Received data:", data);

const decodedData = Buffer.from(data, "base64").toString("ascii");
// Check for EOT packet. If EOT packet is received, then stop reading the characteristic.
if (decodedData.includes(EOT_MARKER)) {
isEOTReceived = true;
fullData += decodedData; // Add decoded data to fullData
break;
}

fullData += decodedData;

// Optional: Handle the case where data might exceed 100 bytes per packet
if (data.length > MTU) {
console.warn(`Received data packet exceeds ${MTU} bytes`);
}
}

console.log("Received full data:", fullData);
return fullData;
} catch (error) {
console.error("Read characteristic error:", error);
throw error; // Rethrow the error for further handling if necessary
}
};
};

export default BleScanner;

Код Raspberry Pi Python. Метод WriteValue() класса RxCharacteristic содержит операторы декодирования print и base 64, упомянутые выше:
import gi

gi.require_version('DBus', '1.0')
from gi.repository import GLib
from gi.repository import Gio
from gi.repository import DBus

import os
import time
import json
import base64
import sys
import subprocess
import configparser
import dbus
import dbus.mainloop.glib
from ble_advertisement import Advertisement
from ble_advertisement import register_ad_cb, register_ad_error_cb
from ble_gatt_server import Service, Characteristic
from ble_gatt_server import register_app_cb, register_app_error_cb

BLUEZ_SERVICE_NAME = 'org.bluez'
DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_MANAGER_IFACE = 'org.bluez.GattManager1'
GATT_CHRC_IFACE = 'org.bluez.GattCharacteristic1'
UART_SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'
UART_RX_CHARACTERISTIC_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'
UART_TX_CHARACTERISTIC_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
LOCAL_NAME = 'Tycho-Hub-1'
mainloop = None

PACKET_SIZE = 100
EOT_MARKER = "\x04" # End-of-Transmission marker, ASCII code for EOT
received_data = [] # Store received data packets

class CasePreservingConfigParser(configparser.ConfigParser):
def optionxform(self, optionstr):
return optionstr

class TxCharacteristic(Characteristic):
def __init__(self, bus, index, service):
Characteristic.__init__(self, bus, index, UART_TX_CHARACTERISTIC_UUID, ['read'], service)
GLib.io_add_watch(sys.stdin, GLib.IO_IN, self.on_console_input)
self.isReadable = True

def on_console_input(self, fd, condition):
s = fd.readline()
if s.isspace():
pass
else:
self.send_tx(s)
return True

def send_tx(self, config_dict):
global tx_data_packets
config_json = json.dumps(config_dict, indent=4)
print(f"Sending config JSON: {config_json}")
total_length = len(config_json)
print(f"Total Length of Data: {total_length} bytes")
# Split data into packets and store in the global list
tx_data_packets = [base64.b64encode(config_json[i:i + PACKET_SIZE].encode()).decode() for i in range(0, total_length, PACKET_SIZE)]
tx_data_packets.append(base64.b64encode(EOT_MARKER.encode()).decode()) # Append base64 encoded EOT marker to the end of the list
print(f"Number of Packets Prepared: {len(tx_data_packets)}")

def ReadValue(self, options):
global tx_data_packets
if not tx_data_packets:
return GLib.Variant('ay', []) # Return empty if no more packets

# Pop the first packet from the list and return it
packet = tx_data_packets.pop(0)
print(f"Sending packet: {packet}")
return GLib.Variant('ay', packet)

def StartNotify(self):
if self.notifying:
return
self.notifying = True

def StopNotify(self):
if not self.notifying:
return
self.notifying = False

class RxCharacteristic(Characteristic):
def __init__(self, bus, index, service):
Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID, ['write'], service)

def WriteValue(self, value, options):
try:
print("Received data: ", value)
print("Received data length: ", len(value))
# Convert the received data to a byte array
byte_array = bytearray(value)
# Decode the received base64 data
value_str = base64.b64decode(byte_array).decode('utf-8')
print("Received base64 decoded data: ", value_str)
received_data.append(value_str) # Append received data to the buffer

# Check for EOT (End of Transmission) marker
if EOT_MARKER in value_str:
received_data_str = ''.join(received_data).split(EOT_MARKER)[0] # Split at EOT and take first part
self.execute_command(received_data_str) # Execute the command with all received data
received_data.clear() # Clear the buffer
except Exception as e:
print(f"Error in WriteValue: {e}")

def execute_command(self, command):
"""
Executes the received command. If the command is "retrieveHubConfig"
then send_hub_config() is called, otherwise if it contains 'cloud_settings'
or 'hub_settings' in its JSON structure, it calls update_hub_config().
Otherwise, executes the command as a shell command.
"""
print(f"Received Command: '{command}'")

if "retrieveHubConfig" in command:
self.send_hub_config()
else:
try:
command_json = json.loads(command)
if 'cloud_settings' in command_json or 'hub_settings' in command_json:
self.update_hub_config(command)
except json.JSONDecodeError:
# Not a JSON command, execute as shell command
self.execute_shell_command(command)

def execute_shell_command(self, command):
"""
Executes a command in the shell.
"""
try:
output = subprocess.check_output(command, shell=True)
print("Shell Command Output:", output.decode())
except subprocess.CalledProcessError as e:
print(f"Error executing shell command: {e}")

def send_hub_config(self):
# Read cloud settings from hubConfig.ini using the case-preserving parser
config = CasePreservingConfigParser()
config.read(os.path.join(os.path.dirname(__file__), 'hubConfig.ini'))

config_dict = {}
# Iterate over sections and their options
for section in config.sections():
config_dict[section] = {}
for option in config.options(section):
config_dict[section][option] = config.get(section, option)

print("Sending hub config...")
self.service.tx_characteristic.send_tx(config_dict)

def update_hub_config(self, settings):
print("In update hub config method")
print(f"Settings: {settings}")

try:
# Parse the JSON string into a dictionary
settings_dict = json.loads(settings)
print(f"Settings dictionary: {settings_dict}")

# Create a CasePreservingConfigParser object and read the existing INI file
config = CasePreservingConfigParser()
config.read("hubConfig.ini")

# Iterate over the dictionary and update the ConfigParser object
for section, options in settings_dict.items():
if not config.has_section(section):
config.add_section(section)

for key, value in options.items():
config.set(section, key, str(value))

print(f"Config parser object after update: {config}")

# Write the updated configuration back to the INI file
with open("hubConfig.ini", "w") as configfile:
config.write(configfile)

print("Updated hubConfig.ini with JSON data")

except json.JSONDecodeError as e:
print(f"Error decoding JSON: {e}")
except configparser.Error as e:
print(f"ConfigParser error: {e}")
except Exception as e:
print(f"General error in update_hub_config: {e}")

class UartService(Service):
def __init__(self, bus, index):
Service.__init__(self, bus, index, UART_SERVICE_UUID, True)
self.tx_characteristic = TxCharacteristic(bus, 0, self)
self.rx_characteristic = RxCharacteristic(bus, 1, self)
self.add_characteristic(self.tx_characteristic)
self.add_characteristic(self.rx_characteristic)

class Application(dbus.service.Object):
def __init__(self, bus):
self.path = '/'
self.services = []
dbus.service.Object.__init__(self, bus, self.path)

def get_path(self):
return dbus.ObjectPath(self.path)

def add_service(self, service):
self.services.append(service)

@dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
def GetManagedObjects(self):
response = {}
for service in self.services:
response[service.get_path()] = service.get_properties()
chrcs = service.get_characteristics()
for chrc in chrcs:
response[chrc.get_path()] = chrc.get_properties()
return response

class UartApplication(Application):
def __init__(self, bus):
Application.__init__(self, bus)
self.add_service(UartService(bus, 0))

class UartAdvertisement(Advertisement):
def __init__(self, bus, index):
Advertisement.__init__(self, bus, index, 'peripheral')
self.add_service_uuid(UART_SERVICE_UUID)
self.add_local_name(LOCAL_NAME)
self.include_tx_power = True

def find_adapter(bus):
remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
DBUS_OM_IFACE)
objects = remote_om.GetManagedObjects()
for o, props in objects.items():
if LE_ADVERTISING_MANAGER_IFACE in props and GATT_MANAGER_IFACE in props:
return o
print('Skip adapter:', o)
return None

def main():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
adapter = find_adapter(bus)
if not adapter:
print('BLE adapter not found')
return

service_manager = dbus.Interface(
bus.get_object(BLUEZ_SERVICE_NAME, adapter),
GATT_MANAGER_IFACE)
ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
LE_ADVERTISING_MANAGER_IFACE)

app = UartApplication(bus)
adv = UartAdvertisement(bus, 0)

mainloop = GLib.MainLoop()

service_manager.RegisterApplication(app.get_path(), {},
reply_handler=register_app_cb,
error_handler=register_app_error_cb)
ad_manager.RegisterAdvertisement(adv.get_path(), {},
reply_handler=register_ad_cb,
error_handler=register_ad_error_cb)

try:
mainloop.run()
except KeyboardInterrupt:
adv.Release()

if __name__ == '__main__':
main()


Подробнее здесь: https://stackoverflow.com/questions/790 ... dic-uart-s
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Python»