parent
25c87fd7a3
commit
8761105264
@ -0,0 +1,273 @@
|
||||
// clang-format off
|
||||
|
||||
/**
|
||||
* Copyright (c) 2016 - 2021, Nordic Semiconductor ASA
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form, except as embedded into a Nordic
|
||||
* Semiconductor ASA integrated circuit in a product or a software update for
|
||||
* such product, must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of Nordic Semiconductor ASA nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* 4. This software, with or without modification, must only be used with a
|
||||
* Nordic Semiconductor ASA integrated circuit.
|
||||
*
|
||||
* 5. Any software provided in binary form under this license must not be reverse
|
||||
* engineered, decompiled, modified and/or disassembled.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nrf_dfu_serial.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "boards.h"
|
||||
#include "app_util_platform.h"
|
||||
#include "nrf_dfu_transport.h"
|
||||
#include "nrf_dfu_req_handler.h"
|
||||
#include "slip.h"
|
||||
#include "nrf_balloc.h"
|
||||
#include "nrf_drv_uart.h"
|
||||
|
||||
#define NRF_LOG_MODULE_NAME nrf_dfu_serial_uart
|
||||
#include "nrf_log.h"
|
||||
NRF_LOG_MODULE_REGISTER();
|
||||
|
||||
/**@file
|
||||
*
|
||||
* @defgroup nrf_dfu_serial_uart DFU Serial UART transport
|
||||
* @ingroup nrf_dfu
|
||||
* @brief Device Firmware Update (DFU) transport layer using UART.
|
||||
*/
|
||||
|
||||
#define NRF_SERIAL_OPCODE_SIZE (sizeof(uint8_t))
|
||||
#define NRF_UART_MAX_RESPONSE_SIZE_SLIP (2 * NRF_SERIAL_MAX_RESPONSE_SIZE + 1)
|
||||
#define RX_BUF_SIZE (64) //to get 64bytes payload
|
||||
#define OPCODE_OFFSET (sizeof(uint32_t) - NRF_SERIAL_OPCODE_SIZE)
|
||||
#define DATA_OFFSET (OPCODE_OFFSET + NRF_SERIAL_OPCODE_SIZE)
|
||||
#define UART_SLIP_MTU (2 * (RX_BUF_SIZE + 1) + 1)
|
||||
#define BALLOC_BUF_SIZE ((CEIL_DIV((RX_BUF_SIZE+OPCODE_SIZE),sizeof(uint32_t))*sizeof(uint32_t)))
|
||||
|
||||
NRF_BALLOC_DEF(m_payload_pool, (UART_SLIP_MTU + 1), NRF_DFU_SERIAL_UART_RX_BUFFERS);
|
||||
|
||||
static nrf_drv_uart_t m_uart = NRF_DRV_UART_INSTANCE(0);
|
||||
static uint8_t m_rx_byte;
|
||||
|
||||
static nrf_dfu_serial_t m_serial;
|
||||
static slip_t m_slip;
|
||||
static uint8_t m_rsp_buf[NRF_UART_MAX_RESPONSE_SIZE_SLIP];
|
||||
static bool m_active;
|
||||
static bool m_waiting_for_buffers = false;
|
||||
|
||||
static nrf_dfu_observer_t m_observer;
|
||||
|
||||
static uint32_t uart_dfu_transport_init(nrf_dfu_observer_t observer);
|
||||
static uint32_t uart_dfu_transport_close(nrf_dfu_transport_t const * p_exception);
|
||||
|
||||
DFU_TRANSPORT_REGISTER(nrf_dfu_transport_t const uart_dfu_transport) =
|
||||
{
|
||||
.init_func = uart_dfu_transport_init,
|
||||
.close_func = uart_dfu_transport_close,
|
||||
};
|
||||
|
||||
static void payload_free(void * p_buf)
|
||||
{
|
||||
uint8_t * p_buf_root = (uint8_t *)p_buf - DATA_OFFSET; //pointer is shifted to point to data
|
||||
nrf_balloc_free(&m_payload_pool, p_buf_root);
|
||||
uint8_t utilization = m_payload_pool.p_stack_limit - m_payload_pool.p_cb->p_stack_pointer;
|
||||
|
||||
if (m_waiting_for_buffers && utilization < NRF_DFU_SERIAL_UART_RX_BUFFERS - 2) {
|
||||
NRF_LOG_INFO("Buffer utilization: %d, resuming.", utilization);
|
||||
|
||||
nrf_gpio_pin_set(RTS_PIN_NUMBER);
|
||||
nrf_gpio_cfg_output(RTS_PIN_NUMBER);
|
||||
m_uart.uarte.p_reg->PSEL.RTS = RTS_PIN_NUMBER;
|
||||
m_waiting_for_buffers = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static ret_code_t rsp_send(uint8_t const * p_data, uint32_t length)
|
||||
{
|
||||
uint32_t slip_len;
|
||||
(void) slip_encode(m_rsp_buf, (uint8_t *)p_data, length, &slip_len);
|
||||
|
||||
return nrf_drv_uart_tx(&m_uart, m_rsp_buf, slip_len);
|
||||
}
|
||||
|
||||
static __INLINE void on_rx_complete(nrf_dfu_serial_t * p_transport, uint8_t * p_data, uint8_t len)
|
||||
{
|
||||
ret_code_t ret_code = NRF_ERROR_TIMEOUT;
|
||||
|
||||
// Check if there is byte to process. Zero length transfer means that RXTO occured.
|
||||
if (len)
|
||||
{
|
||||
ret_code = slip_decode_add_byte(&m_slip, p_data[0]);
|
||||
}
|
||||
|
||||
(void) nrf_drv_uart_rx(&m_uart, &m_rx_byte, 1);
|
||||
|
||||
if (ret_code == NRF_SUCCESS)
|
||||
{
|
||||
nrf_dfu_serial_on_packet_received(p_transport,
|
||||
(uint8_t const *)m_slip.p_buffer,
|
||||
m_slip.current_index);
|
||||
|
||||
uint8_t * p_rx_buf = nrf_balloc_alloc(&m_payload_pool);
|
||||
|
||||
uint8_t utilization = m_payload_pool.p_stack_limit - m_payload_pool.p_cb->p_stack_pointer;
|
||||
|
||||
if (!m_waiting_for_buffers && utilization >= NRF_DFU_SERIAL_UART_RX_BUFFERS - 2) {
|
||||
NRF_LOG_INFO("Buffer utilization: %d, waiting.", utilization);
|
||||
m_uart.uarte.p_reg->PSEL.RTS = NRF_UART_PSEL_DISCONNECTED;
|
||||
nrf_gpio_cfg_output(RTS_PIN_NUMBER);
|
||||
nrf_gpio_pin_set(RTS_PIN_NUMBER);
|
||||
m_waiting_for_buffers = true;
|
||||
}
|
||||
|
||||
if (p_rx_buf == NULL)
|
||||
{
|
||||
NRF_LOG_ERROR("Failed to allocate buffer");
|
||||
return;
|
||||
}
|
||||
NRF_LOG_INFO("Allocated buffer %x", p_rx_buf);
|
||||
// reset the slip decoding
|
||||
m_slip.p_buffer = &p_rx_buf[OPCODE_OFFSET];
|
||||
m_slip.current_index = 0;
|
||||
m_slip.state = SLIP_STATE_DECODING;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void uart_event_handler(nrf_drv_uart_event_t * p_event, void * p_context)
|
||||
{
|
||||
switch (p_event->type)
|
||||
{
|
||||
case NRF_DRV_UART_EVT_RX_DONE:
|
||||
on_rx_complete((nrf_dfu_serial_t*)p_context,
|
||||
p_event->data.rxtx.p_data,
|
||||
p_event->data.rxtx.bytes);
|
||||
break;
|
||||
|
||||
case NRF_DRV_UART_EVT_ERROR:
|
||||
APP_ERROR_HANDLER(p_event->data.error.error_mask);
|
||||
break;
|
||||
|
||||
default:
|
||||
// No action.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t uart_dfu_transport_init(nrf_dfu_observer_t observer)
|
||||
{
|
||||
uint32_t err_code = NRF_SUCCESS;
|
||||
|
||||
if (m_active)
|
||||
{
|
||||
return err_code;
|
||||
}
|
||||
|
||||
NRF_LOG_DEBUG("serial_dfu_transport_init()");
|
||||
|
||||
m_observer = observer;
|
||||
|
||||
err_code = nrf_balloc_init(&m_payload_pool);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
{
|
||||
return err_code;
|
||||
}
|
||||
|
||||
uint8_t * p_rx_buf = nrf_balloc_alloc(&m_payload_pool);
|
||||
|
||||
m_slip.p_buffer = &p_rx_buf[OPCODE_OFFSET];
|
||||
m_slip.current_index = 0;
|
||||
m_slip.buffer_len = UART_SLIP_MTU;
|
||||
m_slip.state = SLIP_STATE_DECODING;
|
||||
|
||||
m_serial.rsp_func = rsp_send;
|
||||
m_serial.payload_free_func = payload_free;
|
||||
m_serial.mtu = UART_SLIP_MTU;
|
||||
m_serial.p_rsp_buf = &m_rsp_buf[NRF_UART_MAX_RESPONSE_SIZE_SLIP -
|
||||
NRF_SERIAL_MAX_RESPONSE_SIZE];
|
||||
m_serial.p_low_level_transport = &uart_dfu_transport;
|
||||
|
||||
nrf_drv_uart_config_t uart_config = NRF_DRV_UART_DEFAULT_CONFIG;
|
||||
|
||||
uart_config.pseltxd = TX_PIN_NUMBER;
|
||||
uart_config.pselrxd = RX_PIN_NUMBER;
|
||||
uart_config.pselcts = CTS_PIN_NUMBER;
|
||||
uart_config.pselrts = RTS_PIN_NUMBER;
|
||||
uart_config.hwfc = NRF_UART_HWFC_ENABLED;
|
||||
uart_config.p_context = &m_serial;
|
||||
|
||||
nrf_gpio_cfg(
|
||||
RTS_PIN_NUMBER,
|
||||
NRF_GPIO_PIN_DIR_OUTPUT,
|
||||
NRF_GPIO_PIN_INPUT_DISCONNECT,
|
||||
NRF_GPIO_PIN_NOPULL,
|
||||
NRF_GPIO_PIN_S0S1,
|
||||
NRF_GPIO_PIN_NOSENSE);
|
||||
|
||||
|
||||
nrf_gpio_pin_write(RTS_PIN_NUMBER, 0);
|
||||
|
||||
err_code = nrf_drv_uart_init(&m_uart, &uart_config, uart_event_handler);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
{
|
||||
NRF_LOG_ERROR("Failed initializing uart");
|
||||
return err_code;
|
||||
}
|
||||
|
||||
err_code = nrf_drv_uart_rx(&m_uart, &m_rx_byte, 1);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
{
|
||||
NRF_LOG_ERROR("Failed initializing rx");
|
||||
}
|
||||
|
||||
NRF_LOG_DEBUG("serial_dfu_transport_init() completed");
|
||||
|
||||
m_active = true;
|
||||
|
||||
if (m_observer)
|
||||
{
|
||||
m_observer(NRF_DFU_EVT_TRANSPORT_ACTIVATED);
|
||||
}
|
||||
|
||||
return err_code;
|
||||
}
|
||||
|
||||
|
||||
static uint32_t uart_dfu_transport_close(nrf_dfu_transport_t const * p_exception)
|
||||
{
|
||||
if ((m_active == true) && (p_exception != &uart_dfu_transport))
|
||||
{
|
||||
nrf_drv_uart_uninit(&m_uart);
|
||||
m_active = false;
|
||||
}
|
||||
|
||||
return NRF_SUCCESS;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include STM32_HAL_H
|
||||
#include TREZOR_BOARD
|
||||
|
||||
static UART_HandleTypeDef urt;
|
||||
|
||||
void ble_comm_init(void) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
|
||||
__HAL_RCC_USART1_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE();
|
||||
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Alternate = GPIO_AF7_USART1;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
|
||||
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
|
||||
|
||||
urt.Init.Mode = UART_MODE_TX_RX;
|
||||
urt.Init.BaudRate = 1000000;
|
||||
urt.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS;
|
||||
urt.Init.OverSampling = UART_OVERSAMPLING_16;
|
||||
urt.Init.Parity = UART_PARITY_NONE, urt.Init.StopBits = UART_STOPBITS_1;
|
||||
urt.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
urt.Instance = USART1;
|
||||
|
||||
HAL_UART_Init(&urt);
|
||||
}
|
||||
|
||||
void ble_comm_send(uint8_t *data, uint32_t len) {
|
||||
HAL_UART_Transmit(&urt, data, len, 10);
|
||||
}
|
||||
|
||||
uint32_t ble_comm_receive(uint8_t *data, uint32_t len) {
|
||||
if (urt.Instance->SR & USART_SR_RXNE) {
|
||||
HAL_StatusTypeDef result = HAL_UART_Receive(&urt, data, len, 30);
|
||||
|
||||
if (result == HAL_OK) {
|
||||
return len;
|
||||
} else {
|
||||
if (urt.RxXferCount == len) {
|
||||
return 0;
|
||||
}
|
||||
return len - urt.RxXferCount - 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
void ble_comm_init(void);
|
||||
|
||||
void ble_comm_send(uint8_t *data, uint32_t len);
|
||||
|
||||
uint32_t ble_comm_receive(uint8_t *data, uint32_t len);
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* This file is part of the Trezor project, https://trezor.io/
|
||||
*
|
||||
* Copyright (c) SatoshiLabs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include STM32_HAL_H
|
||||
#include "dfu.h"
|
||||
#include "comm.h"
|
||||
#include "fwu.h"
|
||||
|
||||
static TFwu sFwu;
|
||||
|
||||
static uint32_t tick_start = 0;
|
||||
|
||||
void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len);
|
||||
static uint8_t readData(uint8_t *data, int maxLen);
|
||||
|
||||
void dfu_init(void) {}
|
||||
|
||||
dfu_result_t dfu_update_process(void) {
|
||||
while (1) {
|
||||
// Can send 4 chars...
|
||||
// (On a microcontroller, you'd use the TX Empty interrupt or test a
|
||||
// register.)
|
||||
|
||||
fwuCanSendData(&sFwu, 4);
|
||||
|
||||
// Data available? Get up to 4 bytes...
|
||||
// (On a microcontroller, you'd use the RX Available interrupt or test a
|
||||
// register.)
|
||||
uint8_t rxBuf[4];
|
||||
uint8_t rxLen = readData(rxBuf, 4);
|
||||
if (rxLen > 0) {
|
||||
fwuDidReceiveData(&sFwu, rxBuf, rxLen);
|
||||
}
|
||||
|
||||
// Give the firmware update module a timeslot to continue the process.
|
||||
EFwuProcessStatus status = fwuYield(&sFwu, 0);
|
||||
|
||||
if (status == FWU_STATUS_COMPLETION) {
|
||||
return DFU_SUCCESS;
|
||||
}
|
||||
|
||||
if (status == FWU_STATUS_FAILURE) {
|
||||
return DFU_FAIL;
|
||||
}
|
||||
|
||||
if (HAL_GetTick() - tick_start > 2000) {
|
||||
return DFU_FAIL;
|
||||
}
|
||||
|
||||
if (fwuIsReadyForChunk(&sFwu)) {
|
||||
return DFU_NEXT_CHUNK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len) {
|
||||
sFwu.commandObject = data;
|
||||
sFwu.commandObjectLen = len;
|
||||
sFwu.dataObject = NULL;
|
||||
sFwu.dataObjectLen = binary_len;
|
||||
sFwu.txFunction = txFunction;
|
||||
sFwu.responseTimeoutMillisec = 2000;
|
||||
|
||||
tick_start = HAL_GetTick();
|
||||
|
||||
// Prepare the firmware update process.
|
||||
fwuInit(&sFwu);
|
||||
|
||||
// Start the firmware update process.
|
||||
fwuExec(&sFwu);
|
||||
|
||||
return dfu_update_process();
|
||||
}
|
||||
|
||||
dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len) {
|
||||
tick_start = HAL_GetTick();
|
||||
|
||||
fwuSendChunk(&sFwu, data, len);
|
||||
|
||||
return dfu_update_process();
|
||||
}
|
||||
|
||||
dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
|
||||
uint8_t *binfile, uint32_t binfile_len) {
|
||||
uint32_t chunk_offset = 0;
|
||||
uint32_t rem_data = binfile_len;
|
||||
|
||||
dfu_result_t res = dfu_update_init(datfile, datfile_len, binfile_len);
|
||||
|
||||
while (res == DFU_NEXT_CHUNK) {
|
||||
// Send the next chunk of the data object.
|
||||
uint32_t chunk_size = 4096;
|
||||
if (rem_data < 4096) {
|
||||
chunk_size = rem_data;
|
||||
rem_data = 0;
|
||||
} else {
|
||||
rem_data -= 4096;
|
||||
}
|
||||
res = dfu_update_chunk(&binfile[chunk_offset], chunk_size);
|
||||
chunk_offset += chunk_size;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len) {
|
||||
ble_comm_send(buf, len);
|
||||
;
|
||||
}
|
||||
|
||||
static uint8_t readData(uint8_t *data, int maxLen) {
|
||||
return ble_comm_receive(data, maxLen);
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
//
|
||||
// main.c
|
||||
// nrf52-dfu
|
||||
//
|
||||
// Sample host application to demonstrate the usage of our C library for the
|
||||
// Nordic firmware update protocol.
|
||||
//
|
||||
// Created by Andreas Schweizer on 30.11.2018.
|
||||
// Copyright © 2018-2019 Classy Code GmbH
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#include STM32_HAL_H
|
||||
#include "dfu.h"
|
||||
#include "fwu.h"
|
||||
|
||||
static TFwu sFwu;
|
||||
static UART_HandleTypeDef urt;
|
||||
|
||||
static uint32_t tick_start = 0;
|
||||
|
||||
void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len);
|
||||
static uint8_t readData(uint8_t *data, int maxLen);
|
||||
|
||||
void dfu_init(void) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
|
||||
__HAL_RCC_USART1_CLK_ENABLE();
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE();
|
||||
|
||||
GPIO_InitStructure.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
|
||||
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
|
||||
GPIO_InitStructure.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStructure.Alternate = GPIO_AF7_USART1;
|
||||
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
|
||||
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
|
||||
|
||||
urt.Init.Mode = UART_MODE_TX_RX;
|
||||
urt.Init.BaudRate = 115200;
|
||||
urt.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS;
|
||||
urt.Init.OverSampling = UART_OVERSAMPLING_16;
|
||||
urt.Init.Parity = UART_PARITY_NONE, urt.Init.StopBits = UART_STOPBITS_1;
|
||||
urt.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
urt.Instance = USART1;
|
||||
|
||||
HAL_UART_Init(&urt);
|
||||
|
||||
// sFwu.commandObject = datfile;
|
||||
// sFwu.commandObjectLen = sizeof(datfile);
|
||||
// sFwu.dataObject = NULL;
|
||||
// sFwu.dataObjectLen = sizeof(binfile);
|
||||
// sFwu.txFunction = txFunction;
|
||||
// sFwu.responseTimeoutMillisec = 5000;
|
||||
}
|
||||
|
||||
dfu_result_t dfu_update_process(void) {
|
||||
while (1) {
|
||||
// Can send 4 chars...
|
||||
// (On a microcontroller, you'd use the TX Empty interrupt or test a
|
||||
// register.)
|
||||
|
||||
fwuCanSendData(&sFwu, 4);
|
||||
|
||||
// Data available? Get up to 4 bytes...
|
||||
// (On a microcontroller, you'd use the RX Available interrupt or test a
|
||||
// register.)
|
||||
uint8_t rxBuf[4];
|
||||
uint8_t rxLen = readData(rxBuf, 4);
|
||||
if (rxLen > 0) {
|
||||
fwuDidReceiveData(&sFwu, rxBuf, rxLen);
|
||||
}
|
||||
|
||||
// Give the firmware update module a timeslot to continue the process.
|
||||
EFwuProcessStatus status = fwuYield(&sFwu, 0);
|
||||
|
||||
if (status == FWU_STATUS_COMPLETION) {
|
||||
return DFU_SUCCESS;
|
||||
}
|
||||
|
||||
if (status == FWU_STATUS_FAILURE) {
|
||||
return DFU_FAIL;
|
||||
}
|
||||
|
||||
if (HAL_GetTick() - tick_start > 2000) {
|
||||
return DFU_FAIL;
|
||||
}
|
||||
|
||||
if (fwuIsReadyForChunk(&sFwu)) {
|
||||
return DFU_NEXT_CHUNK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dfu_result_t dfu_update_init(uint8_t *data, uint32_t len, uint32_t binary_len) {
|
||||
sFwu.commandObject = data;
|
||||
sFwu.commandObjectLen = len;
|
||||
sFwu.dataObject = NULL;
|
||||
sFwu.dataObjectLen = binary_len;
|
||||
sFwu.txFunction = txFunction;
|
||||
sFwu.responseTimeoutMillisec = 2000;
|
||||
|
||||
tick_start = HAL_GetTick();
|
||||
|
||||
// Prepare the firmware update process.
|
||||
fwuInit(&sFwu);
|
||||
|
||||
// Start the firmware update process.
|
||||
fwuExec(&sFwu);
|
||||
|
||||
return dfu_update_process();
|
||||
}
|
||||
|
||||
dfu_result_t dfu_update_chunk(uint8_t *data, uint32_t len) {
|
||||
tick_start = HAL_GetTick();
|
||||
|
||||
fwuSendChunk(&sFwu, data, len);
|
||||
|
||||
return dfu_update_process();
|
||||
}
|
||||
|
||||
dfu_result_t dfu_update_do(uint8_t *datfile, uint32_t datfile_len,
|
||||
uint8_t *binfile, uint32_t binfile_len) {
|
||||
uint32_t chunk_offset = 0;
|
||||
uint32_t rem_data = binfile_len;
|
||||
|
||||
dfu_result_t res = dfu_update_init(datfile, datfile_len, binfile_len);
|
||||
|
||||
while (res == DFU_NEXT_CHUNK) {
|
||||
// Send the next chunk of the data object.
|
||||
uint32_t chunk_size = 4096;
|
||||
if (rem_data < 4096) {
|
||||
chunk_size = rem_data;
|
||||
rem_data = 0;
|
||||
} else {
|
||||
rem_data -= 4096;
|
||||
}
|
||||
res = dfu_update_chunk(&binfile[chunk_offset], chunk_size);
|
||||
chunk_offset += chunk_size;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void txFunction(struct SFwu *fwu, uint8_t *buf, uint8_t len) {
|
||||
HAL_UART_Transmit(&urt, buf, len, 10);
|
||||
}
|
||||
|
||||
static uint8_t readData(uint8_t *data, int maxLen) {
|
||||
HAL_StatusTypeDef result = HAL_UART_Receive(&urt, data, maxLen, 0);
|
||||
|
||||
if (result == HAL_OK) {
|
||||
return maxLen;
|
||||
} else {
|
||||
if (urt.RxXferCount == maxLen) {
|
||||
return 0;
|
||||
}
|
||||
return maxLen - urt.RxXferCount - 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
void ble_comm_init(void) {}
|
||||
|
||||
void ble_comm_send(uint8_t *data, uint32_t len) {}
|
||||
|
||||
uint32_t ble_comm_receive(uint8_t *data, uint32_t len) { return 0; }
|
@ -0,0 +1,6 @@
|
||||
|
||||
void ble_comm_init(void);
|
||||
|
||||
void ble_comm_send(uint8_t *data, uint32_t len);
|
||||
|
||||
uint32_t ble_comm_receive(uint8_t *data, uint32_t len);
|
@ -0,0 +1,22 @@
|
||||
from typing import *
|
||||
|
||||
|
||||
# extmod/modtrezorio/modtrezorio-ble.h
|
||||
def update_init(data: bytes, binsize: int) -> int:
|
||||
"""
|
||||
Initializes the BLE firmware update
|
||||
"""
|
||||
|
||||
|
||||
# extmod/modtrezorio/modtrezorio-ble.h
|
||||
def update_chunk(chunk: bytes) -> int:
|
||||
"""
|
||||
Writes next chunk of BLE firmware update
|
||||
"""
|
||||
|
||||
|
||||
# extmod/modtrezorio/modtrezorio-ble.h
|
||||
def write(self, msg: bytes) -> int:
|
||||
"""
|
||||
Sends message using BLE.
|
||||
"""
|
@ -0,0 +1,17 @@
|
||||
from trezorio import ble
|
||||
|
||||
|
||||
class BleInterface:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def iface_num(self) -> int:
|
||||
return 16
|
||||
|
||||
def write(self, msg: bytes) -> int:
|
||||
return ble.write(self, msg)
|
||||
|
||||
|
||||
# interface used for trezor wire protocol
|
||||
|
||||
iface_ble = BleInterface()
|
@ -0,0 +1,452 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
|
||||
import dbus
|
||||
import dbus.mainloop.glib
|
||||
import dbus.service
|
||||
|
||||
|
||||
class NotConnectedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DBusInvalidArgsException(dbus.exceptions.DBusException):
|
||||
_dbus_error_name = "org.freedesktop.DBus.Error.InvalidArgs"
|
||||
|
||||
|
||||
def format_uuid(uuid):
|
||||
if type(uuid) == int:
|
||||
if uuid > 0xFFFF:
|
||||
raise ValueError("32-bit UUID not supported yet")
|
||||
uuid = "%04X" % uuid
|
||||
return uuid
|
||||
|
||||
|
||||
class TealBlue:
|
||||
def __init__(self):
|
||||
self._bus = dbus.SystemBus()
|
||||
self._bluez = dbus.Interface(
|
||||
self._bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager"
|
||||
)
|
||||
|
||||
def find_adapter(self):
|
||||
# find the first adapter
|
||||
objects = self._bluez.GetManagedObjects()
|
||||
for path in sorted(objects.keys()):
|
||||
interfaces = objects[path]
|
||||
if "org.bluez.Adapter1" not in interfaces:
|
||||
continue
|
||||
properties = interfaces["org.bluez.Adapter1"]
|
||||
return Adapter(self, path, properties)
|
||||
return None # no adapter found
|
||||
|
||||
# copied from:
|
||||
# https://github.com/adafruit/Adafruit_Python_BluefruitLE/blob/master/Adafruit_BluefruitLE/bluez_dbus/provider.py
|
||||
def _print_tree(self):
|
||||
"""Print tree of all bluez objects, useful for debugging."""
|
||||
# This is based on the bluez sample code get-managed-objects.py.
|
||||
objects = self._bluez.GetManagedObjects()
|
||||
for path in sorted(objects.keys()):
|
||||
print("[ %s ]" % (path))
|
||||
interfaces = objects[path]
|
||||
for interface in sorted(interfaces.keys()):
|
||||
if interface in [
|
||||
"org.freedesktop.DBus.Introspectable",
|
||||
"org.freedesktop.DBus.Properties",
|
||||
]:
|
||||
continue
|
||||
print(" %s" % (interface))
|
||||
properties = interfaces[interface]
|
||||
for key in sorted(properties.keys()):
|
||||
print(" %s = %s" % (key, properties[key]))
|
||||
|
||||
|
||||
class Adapter:
|
||||
def __init__(self, teal, path, properties):
|
||||
self._teal = teal
|
||||
self._path = path
|
||||
self._properties = properties
|
||||
self._object = dbus.Interface(
|
||||
teal._bus.get_object("org.bluez", path), "org.bluez.Adapter1"
|
||||
)
|
||||
self._advertisement = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<tealblue.Adapter address=%s>" % (self._properties["Address"])
|
||||
|
||||
def devices(self):
|
||||
"""
|
||||
Returns the devices that BlueZ has discovered.
|
||||
"""
|
||||
objects = self._teal._bluez.GetManagedObjects()
|
||||
for path in sorted(objects.keys()):
|
||||
interfaces = objects[path]
|
||||
if "org.bluez.Device1" not in interfaces:
|
||||
continue
|
||||
properties = interfaces["org.bluez.Device1"]
|
||||
yield Device(self._teal, path, properties)
|
||||
|
||||
def scan(self, timeout_s):
|
||||
return Scanner(self._teal, self, self.devices(), timeout_s)
|
||||
|
||||
@property
|
||||
def advertisement(self):
|
||||
if self._advertisement is None:
|
||||
self._advertisement = Advertisement(self._teal, self)
|
||||
return self._advertisement
|
||||
|
||||
def advertise(self, enable):
|
||||
if enable:
|
||||
self.advertisement.enable()
|
||||
else:
|
||||
self.advertisement.disable()
|
||||
|
||||
def advertise_data(
|
||||
self,
|
||||
local_name=None,
|
||||
service_data=None,
|
||||
service_uuids=None,
|
||||
manufacturer_data=None,
|
||||
):
|
||||
self.advertisement.local_name = local_name
|
||||
self.advertisement.service_data = service_data
|
||||
self.advertisement.service_uuids = service_uuids
|
||||
self.advertisement.manufacturer_data = manufacturer_data
|
||||
|
||||
|
||||
class Scanner:
|
||||
def __init__(self, teal, adapter, initial_devices, timeout_s):
|
||||
self._teal = teal
|
||||
self._adapter = adapter
|
||||
self._was_discovering = adapter._properties[
|
||||
"Discovering"
|
||||
] # TODO get current value, or watch property changes
|
||||
self._queue = queue.Queue()
|
||||
self.timeout_s = timeout_s
|
||||
for device in initial_devices:
|
||||
self._queue.put(device)
|
||||
|
||||
def new_device(path, interfaces):
|
||||
if "org.bluez.Device1" not in interfaces:
|
||||
return
|
||||
if not path.startswith(self._adapter._path + "/"):
|
||||
return
|
||||
# properties = interfaces["org.bluez.Device1"]
|
||||
self._queue.put(Device(self._teal, path, interfaces["org.bluez.Device1"]))
|
||||
|
||||
self._signal_receiver = self._teal._bus.add_signal_receiver(
|
||||
new_device,
|
||||
dbus_interface="org.freedesktop.DBus.ObjectManager",
|
||||
signal_name="InterfacesAdded",
|
||||
)
|
||||
if not self._was_discovering:
|
||||
self._adapter._object.StartDiscovery()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if not self._was_discovering:
|
||||
self._adapter._object.StopDiscovery()
|
||||
self._signal_receiver.remove()
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
try:
|
||||
return self._queue.get(timeout=self.timeout_s)
|
||||
except queue.Empty:
|
||||
raise StopIteration
|
||||
|
||||
|
||||
class Device:
|
||||
def __init__(self, teal, path, properties):
|
||||
self._teal = teal
|
||||
self._path = path
|
||||
self._properties = properties
|
||||
self._services_resolved = threading.Event()
|
||||
self._services = None
|
||||
|
||||
if properties["ServicesResolved"]:
|
||||
self._services_resolved.set()
|
||||
|
||||
# Listen to device events (connect, disconnect, ServicesResolved, ...)
|
||||
self._device = dbus.Interface(
|
||||
teal._bus.get_object("org.bluez", path), "org.bluez.Device1"
|
||||
)
|
||||
self._device_props = dbus.Interface(
|
||||
self._device, "org.freedesktop.DBus.Properties"
|
||||
)
|
||||
self._signal_receiver = self._device_props.connect_to_signal(
|
||||
"PropertiesChanged",
|
||||
lambda itf, ch, inv: self._on_prop_changed(itf, ch, inv),
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
self._signal_receiver.remove()
|
||||
|
||||
def __repr__(self):
|
||||
return "<tealblue.Device address=%s name=%r>" % (self.address, self.name)
|
||||
|
||||
def _on_prop_changed(self, properties, changed_props, invalidated_props):
|
||||
for key, value in changed_props.items():
|
||||
self._properties[key] = value
|
||||
|
||||
if "ServicesResolved" in changed_props:
|
||||
if changed_props["ServicesResolved"]:
|
||||
self._services_resolved.set()
|
||||
else:
|
||||
self._services_resolved.clear()
|
||||
|
||||
def _wait_for_discovery(self):
|
||||
# wait until ServicesResolved is True
|
||||
self._services_resolved.wait()
|
||||
|
||||
def connect(self):
|
||||
self._device.Connect()
|
||||
|
||||
def disconnect(self):
|
||||
self._device.Disconnect()
|
||||
|
||||
def resolve_services(self):
|
||||
self._services_resolved.wait()
|
||||
|
||||
@property
|
||||
def services(self):
|
||||
if not self._services_resolved.is_set():
|
||||
return None
|
||||
if self._services is None:
|
||||
self._services = {}
|
||||
objects = self._teal._bluez.GetManagedObjects()
|
||||
for path in sorted(objects.keys()):
|
||||
if not path.startswith(self._path + "/"):
|
||||
continue
|
||||
if "org.bluez.GattService1" in objects[path]:
|
||||
properties = objects[path]["org.bluez.GattService1"]
|
||||
service = Service(self._teal, self, path, properties)
|
||||
self._services[service.uuid] = service
|
||||
elif "org.bluez.GattCharacteristic1" in objects[path]:
|
||||
properties = objects[path]["org.bluez.GattCharacteristic1"]
|
||||
characterstic = Characteristic(self._teal, self, path, properties)
|
||||
for service in self._services.values():
|
||||
if properties["Service"] == service._path:
|
||||
service.characteristics[characterstic.uuid] = characterstic
|
||||
return self._services
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
return bool(self._properties["Connected"])
|
||||
|
||||
@property
|
||||
def services_resolved(self):
|
||||
return bool(self._properties["ServicesResolved"])
|
||||
|
||||
@property
|
||||
def UUIDs(self):
|
||||
return [str(s) for s in self._properties["UUIDs"]]
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
return str(self._properties["Address"])
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if "Name" not in self._properties:
|
||||
return None
|
||||
return str(self._properties["Name"])
|
||||
|
||||
@property
|
||||
def alias(self):
|
||||
if "Alias" not in self._properties:
|
||||
return None
|
||||
return str(self._properties["Alias"])
|
||||
|
||||
|
||||
class Service:
|
||||
def __init__(self, teal, device, path, properties):
|
||||
self._device = device
|
||||
self._teal = teal
|
||||
self._path = path
|
||||
self._properties = properties
|
||||
self.characteristics = {}
|
||||
|
||||
def __repr__(self):
|
||||
return "<tealblue.Service device=%s uuid=%s>" % (
|
||||
self._device.address,
|
||||
self.uuid,
|
||||
)
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
return str(self._properties["UUID"])
|
||||
|
||||
|
||||
class Characteristic:
|
||||
def __init__(self, teal, device, path, properties):
|
||||
self._device = device
|
||||
self._teal = teal
|
||||
self._path = path
|
||||
self._properties = properties
|
||||
|
||||
self.on_notify = None
|
||||
|
||||
self._char = dbus.Interface(
|
||||
teal._bus.get_object("org.bluez", path), "org.bluez.GattCharacteristic1"
|
||||
)
|
||||
char_props = dbus.Interface(self._char, "org.freedesktop.DBus.Properties")
|
||||
self._signal_receiver = char_props.connect_to_signal(
|
||||
"PropertiesChanged",
|
||||
lambda itf, ch, inv: self._on_prop_changed(itf, ch, inv),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<tealblue.Characteristic device=%s uuid=%s>" % (
|
||||
self._device.address,
|
||||
self.uuid,
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
self._signal_receiver.remove()
|
||||
|
||||
def _on_prop_changed(self, properties, changed_props, invalidated_props):
|
||||
for key, value in changed_props.items():
|
||||
self._properties[key] = bytes(value)
|
||||
|
||||
if "Value" in changed_props and self.on_notify is not None:
|
||||
self.on_notify(self, changed_props["Value"])
|
||||
|
||||
def read(self):
|
||||
return bytes(self._char.ReadValue({}))
|
||||
|
||||
def write(self, value):
|
||||
start = time.time()
|
||||
try:
|
||||
self._char.WriteValue(value, {})
|
||||
except dbus.DBusException as e:
|
||||
if (
|
||||
e.get_dbus_name() == "org.bluez.Error.Failed"
|
||||
and e.get_dbus_message() == "Not connected"
|
||||
):
|
||||
raise NotConnectedError()
|
||||
else:
|
||||
raise # some other error
|
||||
|
||||
# Workaround: if the write took very long, it is possible the connection
|
||||
# broke (without causing an exception). So check whether we are still
|
||||
# connected.
|
||||
# I think this is a bug in BlueZ.
|
||||
if time.time() - start > 0.5:
|
||||
if not self._device._device_props.Get("org.bluez.Device1", "Connected"):
|
||||
raise NotConnectedError()
|
||||
|
||||
def start_notify(self):
|
||||
self._char.StartNotify()
|
||||
|
||||
def stop_notify(self):
|
||||
self._char.StopNotify()
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
return str(self._properties["UUID"])
|
||||
|
||||
|
||||
class Advertisement(dbus.service.Object):
|
||||
PATH = "/com/github/aykevl/pynus/advertisement"
|
||||
|
||||
def __init__(self, teal, adapter):
|
||||
self._teal = teal
|
||||
self._adapter = adapter
|
||||
self._enabled = False
|
||||
self.service_uuids = None
|
||||
self.manufacturer_data = None
|
||||
self.solicit_uuids = None
|
||||
self.service_data = None
|
||||
self.local_name = None
|
||||
self.include_tx_power = None
|
||||
self._manager = dbus.Interface(
|
||||
teal._bus.get_object("org.bluez", self._adapter._path),
|
||||
"org.bluez.LEAdvertisingManager1",
|
||||
)
|
||||
self._adv_enabled = threading.Event()
|
||||
dbus.service.Object.__init__(self, teal._bus, self.PATH)
|
||||
|
||||
def enable(self):
|
||||
if self._enabled:
|
||||
return
|
||||
self._manager.RegisterAdvertisement(
|
||||
dbus.ObjectPath(self.PATH),
|
||||
dbus.Dictionary({}, signature="sv"),
|
||||
reply_handler=self._cb_enabled,
|
||||
error_handler=self._cb_enabled_err,
|
||||
)
|
||||
self._adv_enabled.wait()
|
||||
self._adv_enabled.clear()
|
||||
|
||||
def _cb_enabled(self):
|
||||
self._enabled = True
|
||||
if self._adv_enabled.is_set():
|
||||
raise RuntimeError("called enable() twice")
|
||||
self._adv_enabled.set()
|
||||
|
||||
def _cb_enabled_err(self, err):
|
||||
self._enabled = False
|
||||
if self._adv_enabled.is_set():
|
||||
raise RuntimeError("called enable() twice")
|
||||
self._adv_enabled.set()
|
||||
|
||||
def disable(self):
|
||||
if not self._enabled:
|
||||
return
|
||||
self._bus.UnregisterAdvertisement(self.PATH)
|
||||
self._enabled = False
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@dbus.service.method(
|
||||
"org.freedesktop.DBus.Properties", in_signature="s", out_signature="a{sv}"
|
||||
)
|
||||
def GetAll(self, interface):
|
||||
print("GetAll")
|
||||
if interface != "org.bluez.LEAdvertisement1":
|
||||
raise DBusInvalidArgsException()
|
||||
|
||||
try:
|
||||
properties = {
|
||||
"Type": dbus.String("peripheral"),
|
||||
}
|
||||
if self.service_uuids is not None:
|
||||
properties["ServiceUUIDs"] = dbus.Array(
|
||||
map(format_uuid, self.service_uuids), signature="s"
|
||||
)
|
||||
if self.solicit_uuids is not None:
|
||||
properties["SolicitUUIDs"] = dbus.Array(
|
||||
map(format_uuid, self.solicit_uuids), signature="s"
|
||||
)
|
||||
if self.manufacturer_data is not None:
|
||||
properties["ManufacturerData"] = dbus.Dictionary(
|
||||
{k: v for k, v in self.manufacturer_data.items()}, signature="qv"
|
||||
)
|
||||
if self.service_data is not None:
|
||||
properties["ServiceData"] = dbus.Dictionary(
|
||||
self.service_data, signature="sv"
|
||||
)
|
||||
if self.local_name is not None:
|
||||
properties["LocalName"] = dbus.String(self.local_name)
|
||||
if self.include_tx_power is not None:
|
||||
properties["IncludeTxPower"] = dbus.Boolean(self.include_tx_power)
|
||||
except Exception as e:
|
||||
print("err: ", e)
|
||||
print("properties:", properties)
|
||||
return properties
|
||||
|
||||
@dbus.service.method(
|
||||
"org.bluez.LEAdvertisement1", in_signature="", out_signature=""
|
||||
)
|
||||
def Release(self):
|
||||
self._enabled = True
|
@ -0,0 +1,155 @@
|
||||
# This file is part of the Trezor project.
|
||||
#
|
||||
# Copyright (C) 2012-2022 SatoshiLabs and contributors
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3
|
||||
# as published by the Free Software Foundation.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the License along with this library.
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
import logging
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Iterable, Optional
|
||||
|
||||
from .. import tealblue
|
||||
from . import TransportException
|
||||
from .protocol import ProtocolBasedTransport, ProtocolV1
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..models import TrezorModel
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
NUS_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
|
||||
NUS_CHARACTERISTIC_RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
|
||||
NUS_CHARACTERISTIC_TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
|
||||
|
||||
|
||||
def scan_device(adapter, devices):
|
||||
with adapter.scan(2) as scanner:
|
||||
for device in scanner:
|
||||
if NUS_SERVICE_UUID in device.UUIDs:
|
||||
if device.address not in [d.address for d in devices]:
|
||||
print(f"Found device: {device.name}, {device.address}")
|
||||
devices.append(device)
|
||||
return devices
|
||||
|
||||
|
||||
def lookup_device(adapter):
|
||||
devices = []
|
||||
for device in adapter.devices():
|
||||
if NUS_SERVICE_UUID in device.UUIDs:
|
||||
devices.append(device)
|
||||
return devices
|
||||
|
||||
|
||||
class BleTransport(ProtocolBasedTransport):
|
||||
ENABLED = True
|
||||
PATH_PREFIX = "ble"
|
||||
|
||||
def __init__(self, mac_addr: str, adapter) -> None:
|
||||
|
||||
self.tx = None
|
||||
self.rx = None
|
||||
self.device = mac_addr
|
||||
self.adapter = adapter
|
||||
self.received_data = Queue()
|
||||
|
||||
devices = lookup_device(self.adapter)
|
||||
|
||||
for d in devices:
|
||||
if d.address == mac_addr:
|
||||
self.ble_device = d
|
||||
break
|
||||
|
||||
super().__init__(protocol=ProtocolV1(self))
|
||||
|
||||
def get_path(self) -> str:
|
||||
return "{}:{}".format(self.PATH_PREFIX, self.device)
|
||||
|
||||
def find_debug(self) -> "BleTransport":
|
||||
mac = self.device
|
||||
return BleTransport(f"{mac}")
|
||||
|
||||
@classmethod
|
||||
def enumerate(
|
||||
cls, _models: Optional[Iterable["TrezorModel"]] = None
|
||||
) -> Iterable["BleTransport"]:
|
||||
adapter = tealblue.TealBlue().find_adapter()
|
||||
devices = lookup_device(adapter)
|
||||
|
||||
devices = [d for d in devices if d.connected]
|
||||
|
||||
if len(devices) == 0:
|
||||
print("Scanning...")
|
||||
devices = scan_device(adapter, devices)
|
||||
|
||||
print("Found %d devices" % len(devices))
|
||||
|
||||
for device in devices:
|
||||
print(f"Device: {device.name}, {device.address}")
|
||||
|
||||
return [BleTransport(device.address, adapter) for device in devices]
|
||||
|
||||
# @classmethod
|
||||
# def find_by_path(cls, path: str, prefix_search: bool = False) -> "BleTransport":
|
||||
# try:
|
||||
# path = path.replace(f"{cls.PATH_PREFIX}:", "")
|
||||
# return cls._try_path(path)
|
||||
# except TransportException:
|
||||
# if not prefix_search:
|
||||
# raise
|
||||
#
|
||||
# if prefix_search:
|
||||
# return super().find_by_path(path, prefix_search)
|
||||
# else:
|
||||
# raise TransportException(f"No UDP device at {path}")
|
||||
|
||||
def open(self) -> None:
|
||||
|
||||
if not self.ble_device.connected:
|
||||
print(
|
||||
"Connecting to %s (%s)..."
|
||||
% (self.ble_device.name, self.ble_device.address)
|
||||
)
|
||||
self.ble_device.connect()
|
||||
else:
|
||||
print(
|
||||
"Connected to %s (%s)."
|
||||
% (self.ble_device.name, self.ble_device.address)
|
||||
)
|
||||
|
||||
if not self.ble_device.services_resolved:
|
||||
print("Resolving services...")
|
||||
self.ble_device.resolve_services()
|
||||
|
||||
service = self.ble_device.services[NUS_SERVICE_UUID]
|
||||
self.rx = service.characteristics[NUS_CHARACTERISTIC_RX]
|
||||
self.tx = service.characteristics[NUS_CHARACTERISTIC_TX]
|
||||
|
||||
def on_notify(characteristic, value):
|
||||
self.received_data.put(bytes(value))
|
||||
|
||||
self.tx.on_notify = on_notify
|
||||
self.tx.start_notify()
|
||||
|
||||
def close(self) -> None:
|
||||
pass
|
||||
|
||||
def write_chunk(self, chunk: bytes) -> None:
|
||||
assert self.rx is not None
|
||||
self.rx.write(chunk)
|
||||
|
||||
def read_chunk(self) -> bytes:
|
||||
assert self.tx is not None
|
||||
chunk = self.received_data.get()
|
||||
# LOG.log(DUMP_PACKETS, f"received packet: {chunk.hex()}")
|
||||
if len(chunk) != 64:
|
||||
raise TransportException(f"Unexpected chunk size: {len(chunk)}")
|
||||
return bytearray(chunk)
|
Loading…
Reference in new issue