36 Commits
2.70 ... 2.75

Author SHA1 Message Date
Nic Limper
33f77b2192 gzipped wwwroot 2024-12-08 20:55:59 +01:00
Nic Limper
e079c30c54 version compare bugfix 2024-12-08 20:48:58 +01:00
Nic Limper
eba9f54454 Update update_actions.json 2024-12-08 20:14:00 +01:00
Nic Limper
a0a39e98cd some polishing before release 2024-12-08 19:46:20 +01:00
Jelmer
2144bd58f9 Added binaries for some M2 tag types 2024-12-08 17:57:17 +01:00
Jelmer
fdd87779d7 Merge pull request #404 from OpenEPaperLink/G5-compress
Added G5 encoding
2024-12-08 15:59:29 +01:00
Nic Limper
44514e24d1 fix 2024-12-08 15:36:26 +01:00
Nic Limper
8857ecb669 DATATYPE_IMG_G5 2024-12-08 15:32:03 +01:00
Skip Hansen
1871f53b5a Fix #403: missing esp_ieee802154_receive_handle_done() function call causing stop of ZigBee reception. 2024-12-07 15:11:47 -08:00
Nic Limper
1de47b1133 bump tagtype version numbers 2024-12-07 22:27:23 +01:00
Nic Limper
2e38e9f218 added g5 preview to file editor 2024-12-07 22:25:23 +01:00
Nic Limper
a48511145c added header to g5 compressed buffer 2024-12-07 21:59:39 +01:00
Nic Limper
ae87ac1960 added javascript g5 decoder for previews 2024-12-07 20:46:25 +01:00
Jelmer
d84a5f6e75 Added G5 for Opticon 2024-12-06 13:21:47 +01:00
atc1441
f4025fb18f Added ATC BLE OEPL Sending 2024-12-05 09:45:58 +01:00
Jelmer
9d4e31b01b Added G5 to M2 (zbs243) tag types 2024-12-04 00:48:21 +01:00
atc1441
5950c5df4a Update 56.json 2024-12-01 12:23:07 +01:00
Jelmer
691b64d192 fixed the lut argument for g5-compressed images 2024-12-01 11:34:50 +01:00
Jelmer
573ba8c424 Added fallback, updated types 2024-12-01 10:41:24 +01:00
atc1441
511fa3e5dd Updated Tagtypes to support compression 2024-12-01 02:36:07 +01:00
atc1441
cc3128d22a Create FA.json 2024-11-30 19:09:02 +01:00
Jelmer
78e097738a Added G5 encoding 2024-11-29 15:30:24 +01:00
Jelmer
534c52cebf Update oepl-definitions.h 2024-11-29 00:48:18 +01:00
Skip Hansen
8cf6d01098 Chroma42_8176 v0011 release: Added support for BWR displays. 2024-11-21 10:10:17 -08:00
Skip Hansen
aadbe7652e Chroma v0010 release: Reset invalid NVRAM so we can connect to AP. 2024-11-21 10:08:17 -08:00
Skip Hansen
7735612a16 Enable SubGhz, changed CC1101 pin assigments for the Lilygo T-Panel. 2024-11-16 13:51:02 -08:00
Reletiv
c22de350f6 Added HS tagtypes from TLSR Alpha repo (#398)
* Add skadis mount for nebular

* Added tagtypes used in the Reletiv/OpenEpaperLink_TLSR Repo
* Be aware of the ALPHA state, these are very WIP and feel free to contribute and fix the templates

* Removed STL Due to files being moved to other Repo
2024-11-16 20:01:31 +01:00
Nic Limper
95d5aac01a add support for 3bpp ACeP buffers (7 color epaper) 2024-11-13 15:12:26 +01:00
Jelmer
7a31db91ba Added Opticon 7.5" / 09.json 2024-11-11 22:09:33 +01:00
Jelmer
344ded01ac Create 08.json 2024-11-11 22:08:31 +01:00
Jelmer
4747669df8 Added Opticon 2.9 / 07.json 2024-11-10 19:32:28 +01:00
Jelmer
a532c7c190 Added Opticon 2.2 / 06.json 2024-11-10 19:31:14 +01:00
Nic Limper
a6772c6fae fixes https://github.com/OpenEPaperLink/OpenEPaperLink/issues/389 and https://github.com/OpenEPaperLink/OpenEPaperLink/issues/392 2024-11-08 11:17:56 +01:00
Nic Limper
6ec580d267 Update 03.json 2024-11-05 00:04:41 +01:00
Nic Limper
7805ab2b46 new templates for M2 2.2" and M2 2.6" 2024-11-04 23:53:54 +01:00
Nic Limper
e6401f6840 cosmetic issue on update page 2024-11-03 21:10:49 +01:00
122 changed files with 7783 additions and 3362 deletions

View File

@@ -63,7 +63,7 @@ jobs:
# run: |
# cd ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/
# dir build
# esptool.py --chip esp32h2 merge_bin -o merged-firmware.bin --flash_mode dio --flash_size 4MB --flash_freq 48m 0x0 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/OpenEPaperLink_esp32_C6.bin
# esptool.py --chip esp32h2 merge_bin -o merged-firmware.bin --flash_mode dio --flash_size 4MB --flash_freq 48m 0x0 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/OpenEPaperLink_esp32_H2.bin
# cp merged-firmware.bin ../../espbinaries/OpenEPaperLink_esp32_H2.bin
# - name: Zip web files

View File

@@ -13,6 +13,9 @@ menu "OEPL Hardware config"
config OEPL_HARDWARE_PROFILE_CUSTOM
bool "Custom"
config OEPL_HARDWARE_PROFILE_LILYGO
bool "LILYGO-AP"
endchoice
config OEPL_HARDWARE_UART_TX
@@ -40,6 +43,7 @@ menu "OEPL Hardware config"
default 18 if IDF_TARGET_ESP32C2
default 19 if IDF_TARGET_ESP32C3
default 30 if IDF_TARGET_ESP32C6
default 30 if IDF_TARGET_ESP32H2
config MISO_GPIO
int "CC1101 MISO GPIO"

View File

@@ -17,7 +17,9 @@
#include "radio.h"
#include "sdkconfig.h"
#include "second_uart.h"
#ifdef CONFIG_IDF_TARGET_ESP32C6
#include "soc/lp_uart_reg.h"
#endif
#include "soc/uart_struct.h"
#include "utils.h"
#include <esp_mac.h>
@@ -42,7 +44,7 @@ const uint8_t channelList[6] = {11, 15, 20, 25, 26, 27};
struct pendingData pendingDataArr[MAX_PENDING_MACS];
// VERSION GOES HERE!
uint16_t version = 0x001d;
uint16_t version = 0x001e;
#define RAW_PKT_PADDING 2
@@ -752,7 +754,11 @@ void app_main(void) {
pr("RES>");
pr("RDY>");
#ifdef CONFIG_IDF_TARGET_ESP32C6
ESP_LOGI(TAG, "C6 ready!");
#else
ESP_LOGI(TAG, "H2 ready!");
#endif
housekeepingTimer = getMillis();
while (1) {

View File

@@ -3,7 +3,11 @@
#include <stdint.h>
#define LED1 22
#ifdef CONFIG_IDF_TARGET_ESP32C6
#define LED2 23
#elif CONFIG_IDF_TARGET_ESP32H2
#define LED2 25
#endif
#define PROTO_PAN_ID (0x4447) // PAN ID compression shall be used
#define PROTO_PAN_ID_SUBGHZ (0x1337) // PAN ID compression shall be used

View File

@@ -17,7 +17,9 @@
#include "sdkconfig.h"
// if you get an error about soc/lp_uart_reg.h not being found,
// you didn't choose the right build target. :-)
#ifdef CONFIG_IDF_TARGET_ESP32C6
#include "soc/lp_uart_reg.h"
#endif
#include "soc/uart_struct.h"
#include "utils.h"
#include <esp_mac.h>
@@ -39,7 +41,9 @@ void esp_ieee802154_receive_done(uint8_t *frame, esp_ieee802154_frame_info_t *fr
memcpy(inner_rxPKT, &frame[0], frame[0] + 1);
xQueueSendFromISR(packet_buffer, (void *)&inner_rxPKT, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR_ARG(xHigherPriorityTaskWoken);
esp_ieee802154_receive_sfd_done();
if(esp_ieee802154_receive_handle_done(frame)) {
ESP_EARLY_LOGI(TAG, "esp_ieee802154_receive_handle_done() failed");
}
}
void esp_ieee802154_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_error_t error) {
@@ -50,7 +54,7 @@ void esp_ieee802154_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_erro
void esp_ieee802154_transmit_done(const uint8_t *frame, const uint8_t *ack, esp_ieee802154_frame_info_t *ack_frame_info) {
isInTransmit = 0;
ESP_EARLY_LOGI(TAG, "TX %d", frame[0]);
esp_ieee802154_receive_sfd_done();
esp_ieee802154_receive_handle_done(ack);
}
static bool zigbee_is_enabled = false;
void radio_init(uint8_t ch) {

View File

@@ -19,10 +19,12 @@
#include "proto.h"
#include "sdkconfig.h"
#include "soc/uart_struct.h"
#ifdef CONFIG_IDF_TARGET_ESP32C6
#include "soc/lp_uart_reg.h"
static const char *TAG = "SECOND_UART";
#endif
#include "second_uart.h"
static const char *TAG = "SECOND_UART";
#define BUF_SIZE (1024)
#define RD_BUF_SIZE (BUF_SIZE)

View File

@@ -13,8 +13,11 @@ void uart_printf(const char *format, ...);
#define pr uart_printf
#if defined(CONFIG_OEPL_HARDWARE_PROFILE_DEFAULT)
#define CONFIG_OEPL_HARDWARE_UART_TX 3
#define CONFIG_OEPL_HARDWARE_UART_RX 2
#define CONFIG_OEPL_HARDWARE_UART_TX 3
#define CONFIG_OEPL_HARDWARE_UART_RX 2
#elif defined(CONFIG_OEPL_HARDWARE_PROFILE_LILYGO)
#define CONFIG_OEPL_HARDWARE_UART_TX 24
#define CONFIG_OEPL_HARDWARE_UART_RX 23
#elif defined(CONFIG_OEPL_HARDWARE_PROFILE_POE_AP)
#define CONFIG_OEPL_HARDWARE_UART_TX 5
#define CONFIG_OEPL_HARDWARE_UART_RX 18

View File

@@ -17,7 +17,9 @@
#include "proto.h"
#include "sdkconfig.h"
#include "soc/uart_struct.h"
#ifdef CONFIG_IDF_TARGET_ESP32C6
#include "soc/lp_uart_reg.h"
#endif
#include "nvs_flash.h"
void delay(int ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }

View File

@@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(OpenEPaperLink_esp32_C6)
project(OpenEPaperLink_esp32_H2)

View File

@@ -1,9 +1,3 @@
idf_component_register( SRCS
SRCS "utils.c"
SRCS "second_uart.c"
SRCS "radio.c"
SRCS "SubGigRadio.c"
SRCS "cc1101_radio.c"
SRCS "led.c"
SRCS "main.c"
INCLUDE_DIRS ".")
SRC_DIRS "../../OpenEPaperLink_esp32_C6_AP/main"
INCLUDE_DIRS "../../OpenEPaperLink_esp32_C6_AP/main")

View File

@@ -13,6 +13,9 @@ menu "OEPL Hardware config"
config OEPL_HARDWARE_PROFILE_CUSTOM
bool "Custom"
config OEPL_HARDWARE_PROFILE_LILYGO
bool "LILYGO-AP"
endchoice
config OEPL_HARDWARE_UART_TX

View File

@@ -1,562 +0,0 @@
#include "sdkconfig.h"
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include "esp_log.h"
#include "radio.h"
#include "proto.h"
#include "cc1101_radio.h"
#include "SubGigRadio.h"
void DumpHex(void *AdrIn,int Len);
#define LOGE(format, ... ) \
printf("%s#%d: " format,__FUNCTION__,__LINE__,## __VA_ARGS__)
#if 0
#define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
#define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__)
#define LOG_HEX(x,y) DumpHex(x,y)
#else
#define LOG(format, ... )
#define LOG_RAW(format, ... )
#define LOG_HEX(x,y)
#endif
// SPI Stuff
#if CONFIG_SPI2_HOST
#define HOST_ID SPI2_HOST
#elif CONFIG_SPI3_HOST
#define HOST_ID SPI3_HOST
#endif
// Address Config = No address check
// Base Frequency = xxx.xxx
// CRC Enable = false
// Carrier Frequency = 915.000000
// Channel Number = 0
// Channel Spacing = 199.951172
// Data Rate = 1.19948
// Deviation = 5.157471
// Device Address = 0
// Manchester Enable = false
// Modulated = false
// Modulation Format = ASK/OOK
// PA Ramping = false
// Packet Length = 255
// Packet Length Mode = Reserved
// Preamble Count = 4
// RX Filter BW = 58.035714
// Sync Word Qualifier Mode = No preamble/sync
// TX Power = 10
// Whitening = false
// Rf settings for CC1110
const RfSetting gCW[] = {
{CC1101_PKTCTRL0,0x22}, // PKTCTRL0: Packet Automation Control
{CC1101_FSCTRL1,0x06}, // FSCTRL1: Frequency Synthesizer Control
{CC1101_MDMCFG4,0xF5}, // MDMCFG4: Modem configuration
{CC1101_MDMCFG3,0x83}, // MDMCFG3: Modem Configuration
{CC1101_MDMCFG2,0xb0}, // MDMCFG2: Modem Configuration
{CC1101_DEVIATN,0x15}, // DEVIATN: Modem Deviation Setting
{CC1101_MCSM0,0x18}, // MCSM0: Main Radio Control State Machine Configuration
{CC1101_FOCCFG,0x17}, // FOCCFG: Frequency Offset Compensation Configuration
{CC1101_FSCAL3,0xE9}, // FSCAL3: Frequency Synthesizer Calibration
{CC1101_FSCAL2,0x2A}, // FSCAL2: Frequency Synthesizer Calibration
{CC1101_FSCAL1,0x00}, // FSCAL1: Frequency Synthesizer Calibration
{CC1101_FSCAL0,0x1F}, // FSCAL0: Frequency Synthesizer Calibration
{CC1101_TEST1,0x31}, // TEST1: Various Test Settings
{CC1101_TEST0,0x09}, // TEST0: Various Test Settings
{0xff,0} // end of table
};
// Set Base Frequency to 865.999634
const RfSetting g866Mhz[] = {
{CC1101_FREQ2,0x21}, // FREQ2: Frequency Control Word, High Byte
{CC1101_FREQ1,0x4e}, // FREQ1: Frequency Control Word, Middle Byte
{CC1101_FREQ0,0xc4}, // FREQ0: Frequency Control Word, Low Byte
{0xff,0} // end of table
};
// Set Base Frequency to 863.999756
const RfSetting g864Mhz[] = {
{CC1101_FREQ2,0x21}, // FREQ2: Frequency Control Word, High Byte
{CC1101_FREQ1,0x3b}, // FREQ1: Frequency Control Word, Middle Byte
{CC1101_FREQ0,0x13}, // FREQ0: Frequency Control Word, Low Byte
{0xff,0} // end of table
};
// Set Base Frequency to 902.999756
const RfSetting g903Mhz[] = {
{CC1101_FREQ2,0x22}, // FREQ2: Frequency Control Word, High Byte
{CC1101_FREQ1,0xbb}, // FREQ1: Frequency Control Word, Middle Byte
{CC1101_FREQ0,0x13}, // FREQ0: Frequency Control Word, Low Byte
{0xff,0} // end of table
};
// Seet Base Frequency to 915.000000
const RfSetting g915Mhz[] = {
{CC1101_FREQ2,0x23}, // FREQ2: Frequency Control Word, High Byte
{CC1101_FREQ1,0x31}, // FREQ1: Frequency Control Word, Middle Byte
{CC1101_FREQ0,0x3B}, // FREQ0: Frequency Control Word, Low Byte
{0xff,0} // end of table
};
// Address Config = No address check
// Base Frequency = 901.934937 (adjusted to compensate for the crappy crystal on the CC1101 board)
// CRC Enable = true
// Carrier Frequency = 901.934937
// Channel Number = 0
// Channel Spacing = 199.951172
// Data Rate = 38.3835
// Deviation = 20.629883
// Device Address = ff
// Manchester Enable = false
// Modulated = true
// Modulation Format = GFSK
// PA Ramping = false
// Packet Length = 61
// Packet Length Mode = Variable packet length mode. Packet length configured by the first byte after sync word
// Preamble Count = 4
// RX Filter BW = 101.562500
// Sync Word Qualifier Mode = 30/32 sync word bits detected
// TX Power = 10
// Whitening = false
// The following was generated by setting the spec for Register to "{CC1101_@RN@,0x@VH@},"
const RfSetting gIDF_Basic[] = {
{CC1101_SYNC1,0xC7},
{CC1101_SYNC0,0x0A},
{CC1101_PKTLEN,0x3D},
{CC1101_PKTCTRL0,0x05},
{CC1101_ADDR,0xFF},
{CC1101_FSCTRL1,0x08},
{CC1101_FREQ2,0x22},
{CC1101_FREQ1,0xB1},
{CC1101_FREQ0,0x3B},
{CC1101_MDMCFG4,0xCA},
{CC1101_MDMCFG3,0x83},
{CC1101_MDMCFG2,0x93},
{CC1101_DEVIATN,0x35},
// {CC1101_MCSM0,0x18}, FS_AUTOCAL = 1, PO_TIMEOUT = 2
{CC1101_MCSM0,0x18},
{CC1101_FOCCFG,0x16},
{CC1101_AGCCTRL2,0x43},
{CC1101_FSCAL3,0xEF},
{CC1101_FSCAL2,0x2D},
{CC1101_FSCAL1,0x25},
{CC1101_FSCAL0,0x1F},
{CC1101_TEST2,0x81},
{CC1101_TEST1,0x35},
{CC1101_TEST0,0x09},
{0xff,0} // end of table
};
// RF configuration from Dimitry's orginal code
// Address Config = No address check
// Base Frequency = 902.999756
// CRC Autoflush = false
// CRC Enable = true
// Carrier Frequency = 902.999756
// Channel Number = 0
// Channel Spacing = 335.632324
// Data Format = Normal mode
// Data Rate = 249.939
// Deviation = 165.039063
// Device Address = 22
// Manchester Enable = false
// Modulated = true
// Modulation Format = GFSK
// PA Ramping = false
// Packet Length = 255
// Packet Length Mode = Variable packet length mode. Packet length configured by the first byte after sync word
// Preamble Count = 24
// RX Filter BW = 650.000000
// Sync Word Qualifier Mode = 30/32 sync word bits detected
// TX Power = 0
// Whitening = true
// Rf settings for CC1101
// The following was generated by setting the spec for Register to "{CC1101_@RN@,0x@VH@},"
const RfSetting gDmitry915[] = {
{CC1101_FREQ2,0x22},
{CC1101_FREQ1,0xBB},
{CC1101_FREQ0,0x13},
{CC1101_MDMCFG4,0x1D},
{CC1101_MDMCFG3,0x3B},
{CC1101_MDMCFG2,0x13},
{CC1101_MDMCFG1,0x73},
{CC1101_MDMCFG0,0xA7},
{CC1101_DEVIATN,0x65},
{CC1101_MCSM0,0x18},
{CC1101_FOCCFG,0x1E},
{CC1101_BSCFG,0x1C},
{CC1101_AGCCTRL2,0xC7},
{CC1101_AGCCTRL1,0x00},
{CC1101_AGCCTRL0,0xB0},
{CC1101_FREND1,0xB6},
{CC1101_FSCAL3,0xEA},
{CC1101_FSCAL2,0x2A},
{CC1101_FSCAL1,0x00},
{CC1101_FSCAL0,0x1F},
{CC1101_TEST0,0x09},
{0xff,0} // end of table
};
SubGigData gSubGigData;
int CheckSubGigState(void);
void SubGig_CC1101_reset(void);
void SubGig_CC1101_SetConfig(const RfSetting *pConfig);
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
gSubGigData.RxAvailable = true;
}
// return SUBGIG_ERR_NONE aka ESP_OK aka 0 if CC1101 is detected and all is good
SubGigErr SubGig_radio_init(uint8_t ch)
{
esp_err_t Err;
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 5000000, // SPI clock is 5 MHz!
.queue_size = 7,
.mode = 0, // SPI mode 0
.spics_io_num = -1, // we will use manual CS control
.flags = SPI_DEVICE_NO_DUMMY
};
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_NEGEDGE, // GPIO interrupt type : falling edge
//bit mask of the pins
.pin_bit_mask = 1ULL<<CONFIG_GDO0_GPIO,
//set as input mode
.mode = GPIO_MODE_INPUT,
//enable pull-up mode
.pull_up_en = 1,
.pull_down_en = 0
};
int ErrLine = 0;
SubGigErr Ret = SUBGIG_ERR_NONE;
do {
gpio_reset_pin(CONFIG_CSN_GPIO);
gpio_set_direction(CONFIG_CSN_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(CONFIG_CSN_GPIO, 1);
spi_bus_config_t buscfg = {
.sclk_io_num = CONFIG_SCK_GPIO,
.mosi_io_num = CONFIG_MOSI_GPIO,
.miso_io_num = CONFIG_MISO_GPIO,
.quadwp_io_num = -1,
.quadhd_io_num = -1
};
if((Err = spi_bus_initialize(HOST_ID,&buscfg,SPI_DMA_CH_AUTO)) != 0) {
ErrLine = __LINE__;
break;
}
if((Err = spi_bus_add_device(HOST_ID,&devcfg,&gSpiHndl)) != 0) {
ErrLine = __LINE__;
break;
}
// Configure GDO0, interrupt on falling edge
if((Err = gpio_config(&io_conf)) != 0) {
ErrLine = __LINE__;
break;
}
// Configure GDO2, interrupts disabled
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pin_bit_mask = 1ULL<<CONFIG_GDO2_GPIO;
if((Err = gpio_config(&io_conf)) != 0) {
ErrLine = __LINE__;
break;
}
// install gpio isr service
if((Err = gpio_install_isr_service(0)) != 0) {
ErrLine = __LINE__;
break;
}
//hook isr handler for specific gpio pin
Err = gpio_isr_handler_add(CONFIG_GDO0_GPIO,gpio_isr_handler,
(void*) CONFIG_GDO0_GPIO);
if(Err != 0) {
ErrLine = __LINE__;
break;
}
// Check Chip ID
if(!CC1101_Present()) {
Ret = SUBGIG_CC1101_NOT_FOUND;
break;
}
gSubGigData.Present = true;
SubGig_CC1101_reset();
CC1101_SetConfig(NULL);
SubGig_CC1101_SetConfig(gDmitry915);
#if 0
CC1101_DumpRegs();
#endif
if(ch != 0) {
SubGig_radioSetChannel(ch);
}
// good to go!
} while(false);
if(ErrLine != 0) {
LOG("%s#%d: failed %d\n",__FUNCTION__,ErrLine,Err);
if(Err == 0) {
Ret = ESP_FAIL;
}
}
return Ret;
}
SubGigErr SubGig_radioSetChannel(uint8_t ch)
{
SubGigErr Ret = SUBGIG_ERR_NONE;
RfSetting SetChannr[2] = {
{CC1101_CHANNR,0},
{0xff,0} // end of table
};
do {
if(!gSubGigData.Enabled && gSubGigData.Present && ch != 0) {
gSubGigData.Enabled = true;
LOG("SubGhz enabled\n");
}
if((Ret = CheckSubGigState()) != SUBGIG_ERR_NONE) {
break;
}
if(ch == 0) {
// Disable SubGhz
LOG("SubGhz disabled\n");
gSubGigData.Enabled = false;
break;
}
LOG("Set channel %d\n",ch);
if(ch >= FIRST_866_CHAN && ch < FIRST_866_CHAN + NUM_866_CHANNELS) {
// Base Frequency = 863.999756
// total channels 6 (0 -> 5) (CHANNR 0 -> 15)
// Channel 100 / CHANNR 0: 863.999756
// Channel 101 / CHANNR 3: 865.006 Mhz
// Channel 102 / CHANNR 6: 866.014 Mhz
// Channel 103 / CHANNR 9: 867.020 Mhz
// Channel 104 / CHANNR 12: 868.027 Mhz
// Channel 105 / CHANNR 15: 869.034 Mhz
SubGig_CC1101_SetConfig(g864Mhz);
SetChannr[0].Value = (ch - FIRST_866_CHAN) * 3;
}
else {
// Base Frequency = 902.999756
// Dmitry's orginal code used 25 channels in 915 Mhz
// We don't want to have to scan that many so for OEPL we'll just use 6
// to match 866.
// Channel 200 / CHANNR 0: 903.000 Mhz
// Channel 201 / CHANNR 12: 907.027 Mhz
// Channel 202 / CHANNR 24: 911.054 Mhz
// Channel 203 / CHANNR 24: 915.083 Mhz
// Channel 204 / CHANNR 48: 919.110 Mhz
// Channel 205 / CHANNR 60: 923.138 Mhz
SubGig_CC1101_SetConfig(g903Mhz);
if(ch >= FIRST_915_CHAN && ch < FIRST_915_CHAN + NUM_915_CHANNELS) {
SetChannr[0].Value = (ch - FIRST_915_CHAN) * 12;
}
else {
Ret = SUBGIG_INVALID_CHANNEL;
SetChannr[0].Value = 0; // default to the first channel on 915
}
}
SubGig_CC1101_SetConfig(SetChannr);
CC1101_setRxState();
} while(false);
return Ret;
}
SubGigErr SubGig_radioTx(uint8_t *packet)
{
SubGigErr Ret = SUBGIG_ERR_NONE;
do {
if(gSubGigData.FreqTest) {
break;
}
if((Ret = CheckSubGigState()) != SUBGIG_ERR_NONE) {
break;
}
if(packet[0] < 3 || packet[0] > RADIO_MAX_PACKET_LEN + RAW_PKT_PADDING) {
Ret = SUBGIG_TX_BAD_LEN;
break;
}
// All packets seem to be padded by RAW_PKT_PADDING (2 bytes)
// Remove the padding before sending so the length is correct when received
packet[0] -= RAW_PKT_PADDING;
LOG("Sending %d byte subgig frame:\n",packet[0]);
LOG_HEX(&packet[1],packet[0]);
if(CC1101_Tx(packet)) {
Ret = SUBGIG_TX_FAILED;
}
// Clear RxAvailable, in TX GDO0 deasserts on TX FIFO underflows
gSubGigData.RxAvailable = false;
// restore original len just in case anyone cares
packet[0] += RAW_PKT_PADDING;
} while(false);
return Ret;
}
// returns packet size in bytes data in data
int8_t SubGig_commsRxUnencrypted(uint8_t *data)
{
int RxBytes;
int8_t Ret = 0;
do {
if(CheckSubGigState() != SUBGIG_ERR_NONE) {
break;
}
if(gSubGigData.FreqTest) {
break;
}
CC1101_logState();
if(!gSubGigData.RxAvailable && gpio_get_level(CONFIG_GDO0_GPIO) == 1) {
// Did we miss an interrupt?
if(gpio_get_level(CONFIG_GDO0_GPIO) == 1) {
// Yup!
LOGE("SubGhz lost interrupt\n");
gSubGigData.RxAvailable = true;
}
}
if(gSubGigData.RxAvailable){
gSubGigData.RxAvailable = false;
RxBytes = CC1101_Rx(data,128,NULL,NULL);
if(RxBytes >= 2) {
// NB: RxBytes includes the CRC, deduct it
Ret = (uint8_t) RxBytes - 2;
LOG("Received %d byte subgig frame:\n",Ret);
LOG_HEX(data,Ret);
}
}
} while(false);
return Ret;
}
int CheckSubGigState()
{
int Err = SUBGIG_ERR_NONE;
if(!gSubGigData.Present) {
Err = SUBGIG_CC1101_NOT_FOUND;
}
else if(!gSubGigData.Initialized) {
Err = SUBGIG_NOT_INITIALIZED;
}
else if(!gSubGigData.Enabled) {
Err = SUBGIG_NOT_ENABLED;
}
if(Err != SUBGIG_ERR_NONE) {
LOG("CheckSubGigState: returing %d\n",Err);
}
return Err;
}
SubGigErr SubGig_FreqTest(bool b866Mhz,bool bStart)
{
SubGigErr Err = SUBGIG_ERR_NONE;
#if 0
uint8_t TxData = 0; // len = 0
do {
if((Err = CheckSubGigState()) != SUBGIG_ERR_NONE) {
break;
}
if(bStart) {
LOG_RAW("Starting %sMhz Freq test\n",b866Mhz ? "866" : "915");
SubGig_CC1101_reset();
SubGig_CC1101_SetConfig(gCW);
SubGig_CC1101_SetConfig(b866Mhz ? g866Mhz : g915Mhz);
CC1101_cmdStrobe(CC1101_SIDLE);
CC1101_cmdStrobe(CC1101_SFTX); // flush Tx Fifo
CC1101_cmdStrobe(CC1101_STX);
gRfState = RFSTATE_TX;
gSubGigData.FreqTest = true;
}
else {
LOG_RAW("Ending Freq test\n");
gSubGigData.FreqTest = false;
SubGig_CC1101_reset();
SubGig_CC1101_SetConfig(gSubGigData.pConfig);
}
} while(false);
#endif
return Err;
}
void SubGig_CC1101_reset()
{
gSubGigData.Initialized = false;
gSubGigData.FixedRegsSet = false;
CC1101_reset();
}
void SubGig_CC1101_SetConfig(const RfSetting *pConfig)
{
CC1101_SetConfig(pConfig);
gSubGigData.Initialized = true;
}
void DumpHex(void *AdrIn,int Len)
{
unsigned char *Adr = (unsigned char *) AdrIn;
int i = 0;
int j;
while(i < Len) {
for(j = 0; j < 16; j++) {
if((i + j) == Len) {
break;
}
LOG_RAW("%02x ",Adr[i+j]);
}
LOG_RAW(" ");
for(j = 0; j < 16; j++) {
if((i + j) == Len) {
break;
}
if(isprint(Adr[i+j])) {
LOG_RAW("%c",Adr[i+j]);
}
else {
LOG_RAW(".");
}
}
i += 16;
LOG_RAW("\n");
}
}
#endif // CONFIG_OEPL_SUBGIG_SUPPORT

View File

@@ -1,40 +0,0 @@
#ifndef _SUBGIG_RADIO_H_
#define _SUBGIG_RADIO_H_
//sub-GHz 866 Mhz channels start at 100
#define FIRST_866_CHAN (100)
#define NUM_866_CHANNELS (6)
//sub-GHz 915 Mhz channels start at 200
#define FIRST_915_CHAN (200)
#define NUM_915_CHANNELS (6)
typedef enum {
SUBGIG_ERR_NONE,
SUBGIG_CC1101_NOT_FOUND,
SUBGIG_NOT_INITIALIZED,
SUBGIG_NOT_ENABLED,
SUBGIG_TX_FAILED,
SUBGIG_TX_BAD_LEN,
SUBGIG_INVALID_CHANNEL,
} SubGigErr;
typedef struct {
uint8_t Present:1;
uint8_t Enabled:1;
uint8_t FreqTest:1;
uint8_t RxAvailable:1;
uint8_t Initialized:1;
uint8_t FixedRegsSet:1;
} SubGigData;
extern SubGigData gSubGigData;
SubGigErr SubGig_radio_init(uint8_t ch);
SubGigErr SubGig_radioTx(uint8_t *packet);
SubGigErr SubGig_radioSetChannel(uint8_t ch);
int8_t SubGig_commsRxUnencrypted(uint8_t *data);
SubGigErr SubGig_FreqTest(bool b866Mhz,bool bStart);
#endif // _SUBGIG_RADIO_H_

View File

@@ -1,790 +0,0 @@
// Large portions of this code was copied from:
// https://github.com/nopnop2002/esp-idf-cc1101 with the following copyright
/*
* Copyright (c) 2011 panStamp <contact@panstamp.com>
* Copyright (c) 2016 Tyler Sommer <contact@tylersommer.pro>
*
* This file is part of the CC1101 project.
*
* CC1101 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* CC1101 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 GNU Lesser General Public License
* along with CC1101; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
* USA
*
* Author: Daniel Berenguer
* Creation date: 03/03/2011
*/
#include "sdkconfig.h"
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <driver/spi_master.h>
#include "proto.h"
#include "cc1101_radio.h"
#include "radio.h"
#define ENABLE_LOGGING 0
// LOGA - generic logging, always enabled
#define LOGA(format, ... ) printf(format,## __VA_ARGS__)
// LOGE - error logging, always enabled
#define LOGE(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
#if ENABLE_LOGGING
#define LOG(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
#define LOG_RAW(format, ... ) printf(format,## __VA_ARGS__)
#else
#define LOG(format, ... )
#define LOG_RAW(format, ... )
#endif
#define ENABLE_VERBOSE_LOGGING 0
#if ENABLE_VERBOSE_LOGGING
#define LOGV(format, ... ) printf("%s: " format,__FUNCTION__,## __VA_ARGS__)
#define LOGV_RAW(format, ... ) printf(format,## __VA_ARGS__)
#else
#define LOGV(format, ... )
#define LOGB_RAW(format, ... )
#endif
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include "esp_log.h"
#include "sdkconfig.h"
/**
* RF STATES
*/
enum RFSTATE {
RFSTATE_IDLE = 0,
RFSTATE_RX,
RFSTATE_TX
};
/**
* Type of transfers
*/
#define WRITE_BURST 0x40
#define READ_SINGLE 0x80
#define READ_BURST 0xC0
/**
* Type of register
*/
#define CC1101_CONFIG_REGISTER READ_SINGLE
#define CC1101_STATUS_REGISTER READ_BURST
/**
* PATABLE & FIFO's
*/
#define CC1101_PATABLE 0x3E // PATABLE address
#define CC1101_TXFIFO 0x3F // TX FIFO address
#define CC1101_RXFIFO 0x3F // RX FIFO address
/**
* Command strobes
*/
#define CC1101_SRES 0x30 // Reset CC1101 chip
#define CC1101_SFSTXON 0x31 // Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL=1). If in RX (with CCA):
// Go to a wait state where only the synthesizer is running (for quick RX / TX turnaround).
#define CC1101_SXOFF 0x32 // Turn off crystal oscillator
#define CC1101_SCAL 0x33 // Calibrate frequency synthesizer and turn it off. SCAL can be strobed from IDLE mode without
// setting manual calibration mode (MCSM0.FS_AUTOCAL=0)
#define CC1101_SRX 0x34 // Enable RX. Perform calibration first if coming from IDLE and MCSM0.FS_AUTOCAL=1
#define CC1101_STX 0x35 // In IDLE state: Enable TX. Perform calibration first if MCSM0.FS_AUTOCAL=1.
// If in RX state and CCA is enabled: Only go to TX if channel is clear
#define CC1101_SIDLE 0x36 // Exit RX / TX, turn off frequency synthesizer and exit Wake-On-Radio mode if applicable
#define CC1101_SWOR 0x38 // Start automatic RX polling sequence (Wake-on-Radio) as described in Section 19.5 if
// WORCTRL.RC_PD=0
#define CC1101_SPWD 0x39 // Enter power down mode when CSn goes high
#define CC1101_SFRX 0x3A // Flush the RX FIFO buffer. Only issue SFRX in IDLE or RXFIFO_OVERFLOW states
#define CC1101_SFTX 0x3B // Flush the TX FIFO buffer. Only issue SFTX in IDLE or TXFIFO_UNDERFLOW states
#define CC1101_SWORRST 0x3C // Reset real time clock to Event1 value
#define CC1101_SNOP 0x3D // No operation. May be used to get access to the chip status byte
#define CC1101_STATE_SLEEP 0x00
#define CC1101_STATE_IDLE 0x01
#define CC1101_STATE_XOFF 0x02
#define CC1101_STATE_VCOON_MC 0x03
#define CC1101_STATE_REGON_MC 0x04
#define CC1101_STATE_MANCAL 0x05
#define CC1101_STATE_VCOON 0x06
#define CC1101_STATE_REGON 0x07
#define CC1101_STATE_STARTCAL 0x08
#define CC1101_STATE_BWBOOST 0x09
#define CC1101_STATE_FS_LOCK 0x0A
#define CC1101_STATE_IFADCON 0x0B
#define CC1101_STATE_ENDCAL 0x0C
#define CC1101_STATE_RX 0x0D
#define CC1101_STATE_RX_END 0x0E
#define CC1101_STATE_RX_RST 0x0F
#define CC1101_STATE_TXRX_SWITCH 0x10
#define CC1101_STATE_RXFIFO_OVERFLOW 0x11
#define CC1101_STATE_FSTXON 0x12
#define CC1101_STATE_TX 0x13
#define CC1101_STATE_TX_END 0x14
#define CC1101_STATE_RXTX_SWITCH 0x15
#define CC1101_STATE_TXFIFO_UNDERFLOW 0x16
// Masks for first byte read from RXFIFO
#define CC1101_NUM_RXBYTES_MASK 0x7f
#define CC1101_RXFIFO_OVERFLOW_MASK 0x80
// Masks for last byte read from RXFIFO
#define CC1101_LQI_MASK 0x7f
#define CC1101_CRC_OK_MASK 0x80
// IOCFG2 GDO2: high when TX FIFO at or above the TX FIFO threshold
#define CC1101_DEFVAL_IOCFG2 0x02
// IOCFG1 GDO1: High impedance (3-state)
#define CC1101_DEFVAL_IOCFG1 0x2E
// GDO0 goes high when sync word has been sent / received, and
// goes low at the end of the packet.
// In TX mode the pin will go low if the TX FIFO underflows.
#define CC1101_DEFVAL_IOCFG0 0x06
// Threshold = 32 bytes (1/2 of FIFO len)
#define CC1101_DEFVAL_FIFOTHR 0x07
#define CC1101_DEFVAL_RCCTRL1 0x41
#define CC1101_DEFVAL_RCCTRL0 0x00
#define CC1101_DEFVAL_AGCTEST 0x3F
#define CC1101_DEFVAL_MCSM1 0x20
#define CC1101_DEFVAL_WORCTRL 0xFB
#define CC1101_DEFVAL_FSCTRL0 0
#define CC1101_DEFVAL_PATABLE 0xc0 // full power
RfSetting gFixedConfig[] = {
{CC1101_IOCFG2,CC1101_DEFVAL_IOCFG2},
{CC1101_IOCFG1,CC1101_DEFVAL_IOCFG1},
{CC1101_IOCFG0,CC1101_DEFVAL_IOCFG0},
{CC1101_FIFOTHR,CC1101_DEFVAL_FIFOTHR},
{CC1101_FSCTRL0,CC1101_DEFVAL_FSCTRL0},
{CC1101_RCCTRL1,CC1101_DEFVAL_RCCTRL1},
{CC1101_RCCTRL0,CC1101_DEFVAL_RCCTRL0},
{CC1101_MCSM1,CC1101_DEFVAL_MCSM1},
{CC1101_WORCTRL,CC1101_DEFVAL_WORCTRL},
{0xff,0},
};
void CC1101_readBurstReg(uint8_t *buffer,uint8_t regAddr,uint8_t len);
void CC1101_cmdStrobe(uint8_t cmd);
void CC1101_wakeUp(void);
uint8_t CC1101_readReg(uint8_t regAddr, uint8_t regType);
void CC1101_writeReg(uint8_t regAddr, uint8_t value);
void CC1101_setTxState(void);
void setIdleState(void);
spi_device_handle_t gSpiHndl;
#define readConfigReg(regAddr) CC1101_readReg(regAddr, CC1101_CONFIG_REGISTER)
#define readStatusReg(regAddr) CC1101_readReg(regAddr, CC1101_STATUS_REGISTER)
#define flushRxFifo() CC1101_cmdStrobe(CC1101_SFRX)
#define flushTxFifo() CC1101_cmdStrobe(CC1101_SFTX)
const char *RegNamesCC1101[] = {
"IOCFG2", // 0x00 GDO2 output pin configuration
"IOCFG1", // 0x01 GDO1 output pin configuration
"IOCFG0", // 0x02 GDO0 output pin configuration
"FIFOTHR", // 0x03 RX FIFO and TX FIFO thresholds
"SYNC1", // 0x04 Sync word, high INT8U
"SYNC0", // 0x05 Sync word, low INT8U
"PKTLEN", // 0x06 Packet length
"PKTCTRL1", // 0x07 Packet automation control
"PKTCTRL0", // 0x08 Packet automation control
"ADDR", // 0x09 Device address
"CHANNR", // 0x0A Channel number
"FSCTRL1", // 0x0B Frequency synthesizer control
"FSCTRL0", // 0x0C Frequency synthesizer control
"FREQ2", // 0x0D Frequency control word, high INT8U
"FREQ1", // 0x0E Frequency control word, middle INT8U
"FREQ0", // 0x0F Frequency control word, low INT8U
"MDMCFG4", // 0x10 Modem configuration
"MDMCFG3", // 0x11 Modem configuration
"MDMCFG2", // 0x12 Modem configuration
"MDMCFG1", // 0x13 Modem configuration
"MDMCFG0", // 0x14 Modem configuration
"DEVIATN", // 0x15 Modem deviation setting
"MCSM2", // 0x16 Main Radio Control State Machine configuration
"MCSM1", // 0x17 Main Radio Control State Machine configuration
"MCSM0", // 0x18 Main Radio Control State Machine configuration
"FOCCFG", // 0x19 Frequency Offset Compensation configuration
"BSCFG", // 0x1A Bit Synchronization configuration
"AGCCTRL2", // 0x1B AGC control
"AGCCTRL1", // 0x1C AGC control
"AGCCTRL0", // 0x1D AGC control
"WOREVT1", // 0x1E High INT8U Event 0 timeout
"WOREVT0", // 0x1F Low INT8U Event 0 timeout
"WORCTRL", // 0x20 Wake On Radio control
"FREND1", // 0x21 Front end RX configuration
"FREND0", // 0x22 Front end TX configuration
"FSCAL3", // 0x23 Frequency synthesizer calibration
"FSCAL2", // 0x24 Frequency synthesizer calibration
"FSCAL1", // 0x25 Frequency synthesizer calibration
"FSCAL0", // 0x26 Frequency synthesizer calibration
"RCCTRL1", // 0x27 RC oscillator configuration
"RCCTRL0", // 0x28 RC oscillator configuration
"FSTEST", // 0x29 Frequency synthesizer calibration control
"PTEST", // 0x2A Production test
"AGCTEST", // 0x2B AGC test
"TEST2", // 0x2C Various test settings
"TEST1", // 0x2D Various test settings
"TEST0", // 0x2E Various test settings
"0x2f", // 0x2f
//CC1101 Strobe commands
"SRES", // 0x30 Reset chip.
"SFSTXON", // 0x31 Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL=1).
"SXOFF", // 0x32 Turn off crystal oscillator.
"SCAL", // 0x33 Calibrate frequency synthesizer and turn it off
"SRX", // 0x34 Enable RX. Perform calibration first if coming from IDLE and
"STX", // 0x35 In IDLE state: Enable TX. Perform calibration first if
"SIDLE", // 0x36 Exit RX / TX, turn off frequency synthesizer and exit
"SAFC", // 0x37 Perform AFC adjustment of the frequency synthesizer
"SWOR", // 0x38 Start automatic RX polling sequence (Wake-on-Radio)
"SPWD", // 0x39 Enter power down mode when CSn goes high.
"SFRX", // 0x3A Flush the RX FIFO buffer.
"SFTX", // 0x3B Flush the TX FIFO buffer.
"SWORRST", // 0x3C Reset real time clock.
"SNOP", // 0x3D No operation. May be used to pad strobe commands to two
"PATABLE" // 0x3E
};
// SPI Stuff
#if CONFIG_SPI2_HOST
#define HOST_ID SPI2_HOST
#elif CONFIG_SPI3_HOST
#define HOST_ID SPI3_HOST
#endif
/*
* RF state
*/
static uint8_t gRfState;
#define cc1101_Select() gpio_set_level(CONFIG_CSN_GPIO, LOW)
#define cc1101_Deselect() gpio_set_level(CONFIG_CSN_GPIO, HIGH)
#define wait_Miso() while(gpio_get_level(CONFIG_MISO_GPIO)>0)
#define getGDO0state() gpio_get_level(CONFIG_GDO0_GPIO)
#define wait_GDO0_high() while(!getGDO0state())
#define wait_GDO0_low() while(getGDO0state())
#define getGDO2state() gpio_get_level(CONFIG_GDO2_GPIO)
#define wait_GDO2_low() while(getGDO2state())
/**
* Arduino Macros
*/
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) ((bitvalue) ? bitSet(value, bit) : bitClear(value, bit))
#define delayMicroseconds(us) esp_rom_delay_us(us)
#define LOW 0
#define HIGH 1
int32_t gFreqErrSum;
uint8_t gFreqErrSumCount;
int8_t gFreqCorrection;
bool spi_write_byte(uint8_t* Dataout,size_t DataLength)
{
spi_transaction_t SPITransaction;
if(DataLength > 0) {
memset(&SPITransaction,0,sizeof(spi_transaction_t));
SPITransaction.length = DataLength * 8;
SPITransaction.tx_buffer = Dataout;
SPITransaction.rx_buffer = NULL;
spi_device_transmit(gSpiHndl,&SPITransaction);
}
return true;
}
bool spi_read_byte(uint8_t* Datain,uint8_t* Dataout,size_t DataLength)
{
spi_transaction_t SPITransaction;
if(DataLength > 0) {
memset(&SPITransaction,0,sizeof(spi_transaction_t));
SPITransaction.length = DataLength * 8;
SPITransaction.tx_buffer = Dataout;
SPITransaction.rx_buffer = Datain;
spi_device_transmit(gSpiHndl,&SPITransaction);
}
return true;
}
uint8_t spi_transfer(uint8_t address)
{
uint8_t datain[1];
uint8_t dataout[1];
dataout[0] = address;
//spi_write_byte(dev, dataout, 1 );
//spi_read_byte(datain, dataout, 1 );
spi_transaction_t SPITransaction;
memset(&SPITransaction,0,sizeof(spi_transaction_t));
SPITransaction.length = 8;
SPITransaction.tx_buffer = dataout;
SPITransaction.rx_buffer = datain;
spi_device_transmit(gSpiHndl,&SPITransaction);
return datain[0];
}
/**
* CC1101_wakeUp
*
* Wake up CC1101 from Power Down state
*/
void CC1101_wakeUp(void)
{
cc1101_Select();
wait_Miso();
cc1101_Deselect();
}
/**
* CC1101_writeReg
*
* Write single register into the CC1101 IC via SPI
*
* @param regAddr Register address
* @param value Value to be writen
*/
void CC1101_writeReg(uint8_t regAddr, uint8_t value)
{
if(regAddr < 0x3f) {
LOGV("0x%x -> %s(0x%x)\n",value,RegNamesCC1101[regAddr],regAddr);
}
else {
LOGV("0x%x -> 0x%x\n",value,regAddr);
}
cc1101_Select(); // Select CC1101
wait_Miso(); // Wait until MISO goes low
spi_transfer(regAddr); // Send register address
spi_transfer(value); // Send value
cc1101_Deselect(); // Deselect CC1101
}
/**
* CC1101_cmdStrobe
*
* Send command strobe to the CC1101 IC via SPI
*
* @param cmd Command strobe
*/
void CC1101_cmdStrobe(uint8_t cmd)
{
cc1101_Select();
wait_Miso();
spi_transfer(cmd);
cc1101_Deselect();
}
/**
* CC1101_readReg
*
* Read CC1101 register via SPI
*
* @param regAddr Register address
* @param regType Type of register: CONFIG_REGISTER or STATUS_REGISTER
*
* Return:
* Data uint8_t returned by the CC1101 IC
*/
uint8_t CC1101_readReg(uint8_t regAddr,uint8_t regType)
{
uint8_t addr, val;
addr = regAddr | regType;
cc1101_Select();
wait_Miso();
spi_transfer(addr);
val = spi_transfer(0x00); // Read result
cc1101_Deselect();
return val;
}
/**
* CC1101_readBurstReg
*
* Read burst data from CC1101 via SPI
*
* @param buffer Buffer where to copy the result to
* @param regAddr Register address
* @param len Data length
*/
void CC1101_readBurstReg(uint8_t *buffer,uint8_t regAddr,uint8_t len)
{
uint8_t addr, i;
addr = regAddr | READ_BURST;
cc1101_Select();
wait_Miso();
spi_transfer(addr); // Send register address
for(i = 0; i < len; i++) {
buffer[i] = spi_transfer(0x00); // Read result uint8_t by uint8_t
}
cc1101_Deselect();
}
/**
* reset
*
* Reset CC1101
*/
void CC1101_reset(void)
{
// See sectin 19.1.2 of the CC1101 spec sheet for reasons for the following
cc1101_Deselect();
delayMicroseconds(5);
cc1101_Select();
delayMicroseconds(10);
cc1101_Deselect();
delayMicroseconds(41);
cc1101_Select();
// Wait until MISO goes low indicating XOSC stable
wait_Miso();
spi_transfer(CC1101_SRES); // Send reset command strobe
wait_Miso();
cc1101_Deselect();
}
/**
* CC1101_setRxState
*
* Enter Rx state
*/
void CC1101_setRxState(void)
{
CC1101_cmdStrobe(CC1101_SRX);
gRfState = RFSTATE_RX;
}
/**
* CC1101_setTxState
*
* Enter Tx state
*/
void CC1101_setTxState(void)
{
CC1101_cmdStrobe(CC1101_STX);
gRfState = RFSTATE_TX;
}
void CC1101_DumpRegs()
{
#if ENABLE_LOGGING
uint8_t regAddr;
uint8_t value;
LOG("\n");
for(regAddr = 0; regAddr < 0x2f; regAddr++) {
value = CC1101_readReg(regAddr,READ_SINGLE);
LOG("%02x %s: 0x%02X\n",regAddr,RegNamesCC1101[regAddr],value);
}
#if 0
for(regAddr = 0; regAddr < 0x2f; regAddr++) {
value = CC1101_readReg(regAddr,READ_SINGLE);
LOG("<Register><Name>%s</Name><Value>0x%02X</Value></Register>\n",RegNamesCC1101[regAddr],value);
}
#endif
#endif
}
bool CC1101_Tx(uint8_t *TxData)
{
bool Ret = false;
int ErrLine = 0;
spi_transaction_t SPITransaction;
uint8_t BytesSent = 0;
uint8_t Bytes2Send;
uint8_t len;
uint8_t CanSend;
esp_err_t Err;
do {
// The first byte in the buffer is the number of data bytes to send,
// we also need to send the first byte
len = 1 + *TxData;
memset(&SPITransaction,0,sizeof(spi_transaction_t));
SPITransaction.tx_buffer = TxData;
setIdleState();
flushTxFifo();
while(BytesSent < len) {
Bytes2Send = len - BytesSent;
if(BytesSent == 0) {
// First chunk, the FIFO is empty and can take 64 bytes
if(Bytes2Send > 64) {
Bytes2Send = 64;
}
}
else {
// Not the first chunk, we can only send FIFO_THRESHOLD bytes
// and only when GDO2 says we can
if(getGDO2state()) {
wait_GDO2_low();
}
CanSend = readStatusReg(CC1101_TXBYTES);
if(CanSend & 0x80) {
LOGE("TX FIFO underflow, BytesSent %d\n",BytesSent);
ErrLine = __LINE__;
break;
}
CanSend = 64 - CanSend;
if(CanSend == 0) {
LOGE("CanSend == 0, GDO2 problem\n");
ErrLine = __LINE__;
break;
}
if(Bytes2Send > CanSend) {
Bytes2Send = CanSend;
}
}
SPITransaction.length = Bytes2Send * 8;
SPITransaction.rxlength = 0;
cc1101_Select();
wait_Miso();
spi_transfer(CC1101_TXFIFO | WRITE_BURST);
if((Err = spi_device_transmit(gSpiHndl,&SPITransaction)) != ESP_OK) {
ErrLine = __LINE__;
LOGE("spi_device_transmit failed %d\n",Err);
break;
}
cc1101_Deselect();
// LOG("Sending %d bytes\n",Bytes2Send);
if(BytesSent == 0) {
// some or all of the tx data has been written to the FIFO,
// start transmitting
// LOG("Start tx\n");
CC1101_setTxState();
// Wait for the sync word to be transmitted
wait_GDO0_high();
}
SPITransaction.tx_buffer += Bytes2Send;
BytesSent += Bytes2Send;
}
// Wait until the end of the TxData transmission
wait_GDO0_low();
Ret = true;
} while(false);
setIdleState();
CC1101_setRxState();
if(ErrLine != 0) {
LOGE("%s#%d: failure\n",__FUNCTION__,ErrLine);
}
return Ret;
}
// Called when GDO0 goes low, i.e. end of packet.
// Everything has been received.
// NB: this means the entire packet must fit in the FIFO so maximum
// message length is 64 bytes.
int CC1101_Rx(uint8_t *RxBuf,size_t RxBufLen,uint8_t *pRssi,uint8_t *pLqi)
{
uint8_t rxBytes = readStatusReg(CC1101_RXBYTES);
uint8_t Rssi;
uint8_t Lqi;
int Ret;
int8_t FreqErr;
int8_t FreqCorrection;
// Any data waiting to be read and no overflow?
do {
if(rxBytes & CC1101_RXFIFO_OVERFLOW_MASK) {
LOGE("RxFifo overflow\n");
Ret = -2;
break;
}
if(rxBytes < 2) {
// should have at least 2 bytes, packet len and one byte of data
LOGE("Internal error, rxBytes = %d\n",rxBytes);
Ret = -2;
break;
}
// Get packet length
Ret = readConfigReg(CC1101_RXFIFO);
if(Ret > RxBufLen) {
// Toss the data
LOGE("RxBuf too small %d < %d\n",RxBufLen,Ret);
Ret = -1;
break;
}
// Read the data
CC1101_readBurstReg(RxBuf,CC1101_RXFIFO,Ret);
// Read RSSI
Rssi = readConfigReg(CC1101_RXFIFO);
// Read LQI and CRC_OK
Lqi = readConfigReg(CC1101_RXFIFO);
if(!(Lqi & CC1101_CRC_OK_MASK)) {
// Crc error, ignore the packet
LOG("Ignoring %d byte packet, CRC error\n",Ret);
Ret = 0;
break;
}
// CRC is valid
if(pRssi != NULL) {
*pRssi = Rssi;
}
if(pLqi != NULL) {
*pLqi = Lqi & CC1101_LQI_MASK;
}
FreqErr = (int8_t) CC1101_readReg(CC1101_FREQEST,CC1101_STATUS_REGISTER);
if(FreqErr != 0 && gFreqErrSumCount < 255) {
gFreqErrSum += FreqErr + gFreqCorrection;
gFreqErrSumCount++;
FreqCorrection = (int8_t) (gFreqErrSum / gFreqErrSumCount);
if(gFreqCorrection != FreqCorrection) {
LOGA("FreqCorrection %d -> %d\n",gFreqCorrection,FreqCorrection);
gFreqCorrection = FreqCorrection;
CC1101_writeReg(CC1101_FSCTRL0,gFreqCorrection);
}
if(gFreqErrSumCount == 255) {
LOGA("Final FreqCorrection %d\n",gFreqCorrection);
}
}
} while(false);
setIdleState();
flushRxFifo();
CC1101_setRxState();
return Ret;
}
bool CC1101_Present()
{
bool Ret = false;
uint8_t PartNum = CC1101_readReg(CC1101_PARTNUM, CC1101_STATUS_REGISTER);
uint8_t ChipVersion = CC1101_readReg(CC1101_VERSION, CC1101_STATUS_REGISTER);
if(PartNum == 0 && (ChipVersion == 20 || ChipVersion == 4)) {
LOGA("CC1101 detected\n");
Ret = true;
}
else {
if(PartNum != 0) {
LOGA("Invalid PartNum 0x%x\n",PartNum);
}
else {
LOGA("Invalid or unsupported ChipVersion 0x%x\n",ChipVersion);
}
}
return Ret;
}
void CC1101_SetConfig(const RfSetting *pConfig)
{
int i;
uint8_t RegWasSet[CC1101_TEST0 + 1];
uint8_t Reg;
memset(RegWasSet,0,sizeof(RegWasSet));
setIdleState();
if(pConfig == NULL) {
// Just set the fixed registers
LOG("Setting fixed registers\n");
for(i = 0; (Reg = gFixedConfig[i].Reg) != 0xff; i++) {
CC1101_writeReg(Reg,gFixedConfig[i].Value);
}
// Set TX power
CC1101_writeReg(CC1101_PATABLE,CC1101_DEFVAL_PATABLE);
}
else {
for(i = 0; (Reg = gFixedConfig[i].Reg) != 0xff; i++) {
RegWasSet[Reg] = 1;
}
while((Reg = pConfig->Reg) != 0xff) {
if(RegWasSet[Reg] == 1) {
LOG("%s value ignored\n",RegNamesCC1101[Reg]);
}
else {
if(RegWasSet[Reg] == 2) {
LOG("%s value set before\n",RegNamesCC1101[Reg]);
}
CC1101_writeReg(pConfig->Reg,pConfig->Value);
RegWasSet[Reg] = 2;
}
pConfig++;
}
#if 0
for(Reg = 0; Reg <= CC1101_TEST0; Reg++) {
if(RegWasSet[Reg] == 0) {
LOG("%s value not set\n",RegNamesCC1101[Reg]);
}
}
#endif
}
}
void setIdleState()
{
uint8_t MarcState;
CC1101_cmdStrobe(CC1101_SIDLE);
// Wait for it
do {
MarcState = readStatusReg(CC1101_MARCSTATE);
} while(MarcState != CC1101_STATE_IDLE);
}
void CC1101_logState()
{
static uint8_t LastMarcState = 0xff;
uint8_t MarcState;
MarcState = readStatusReg(CC1101_MARCSTATE);
if(LastMarcState != MarcState) {
LOG("MarcState 0x%x -> 0x%x\n",LastMarcState,MarcState);
LastMarcState = MarcState;
}
}
#endif // CONFIG_OEPL_SUBGIG_SUPPORT

View File

@@ -1,118 +0,0 @@
// Large portions of this code was copied from:
// https://github.com/nopnop2002/esp-idf-cc1101 with the following copyright
/*
* Copyright (c) 2011 panStamp <contact@panstamp.com>
* Copyright (c) 2016 Tyler Sommer <contact@tylersommer.pro>
*
* This file is part of the CC1101 project.
*
* CC1101 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* CC1101 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 GNU Lesser General Public License
* along with CC1101; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
* USA
*
* Author: Daniel Berenguer
* Creation date: 03/03/2011
*/
#ifndef __CC1101_RADIO_H_
#define __CC1101_RADIO_H_
/**
* CC1101 configuration registers
*/
#define CC1101_IOCFG2 0x00 // GDO2 Output Pin Configuration
#define CC1101_IOCFG1 0x01 // GDO1 Output Pin Configuration
#define CC1101_IOCFG0 0x02 // GDO0 Output Pin Configuration
#define CC1101_FIFOTHR 0x03 // RX FIFO and TX FIFO Thresholds
#define CC1101_SYNC1 0x04 // Sync Word, High Byte
#define CC1101_SYNC0 0x05 // Sync Word, Low Byte
#define CC1101_PKTLEN 0x06 // Packet Length
#define CC1101_PKTCTRL1 0x07 // Packet Automation Control
#define CC1101_PKTCTRL0 0x08 // Packet Automation Control
#define CC1101_ADDR 0x09 // Device Address
#define CC1101_CHANNR 0x0A // Channel Number
#define CC1101_FSCTRL1 0x0B // Frequency Synthesizer Control
#define CC1101_FSCTRL0 0x0C // Frequency Synthesizer Control
#define CC1101_FREQ2 0x0D // Frequency Control Word, High Byte
#define CC1101_FREQ1 0x0E // Frequency Control Word, Middle Byte
#define CC1101_FREQ0 0x0F // Frequency Control Word, Low Byte
#define CC1101_MDMCFG4 0x10 // Modem Configuration
#define CC1101_MDMCFG3 0x11 // Modem Configuration
#define CC1101_MDMCFG2 0x12 // Modem Configuration
#define CC1101_MDMCFG1 0x13 // Modem Configuration
#define CC1101_MDMCFG0 0x14 // Modem Configuration
#define CC1101_DEVIATN 0x15 // Modem Deviation Setting
#define CC1101_MCSM2 0x16 // Main Radio Control State Machine Configuration
#define CC1101_MCSM1 0x17 // Main Radio Control State Machine Configuration
#define CC1101_MCSM0 0x18 // Main Radio Control State Machine Configuration
#define CC1101_FOCCFG 0x19 // Frequency Offset Compensation Configuration
#define CC1101_BSCFG 0x1A // Bit Synchronization Configuration
#define CC1101_AGCCTRL2 0x1B // AGC Control
#define CC1101_AGCCTRL1 0x1C // AGC Control
#define CC1101_AGCCTRL0 0x1D // AGC Control
#define CC1101_WOREVT1 0x1E // High Byte Event0 Timeout
#define CC1101_WOREVT0 0x1F // Low Byte Event0 Timeout
#define CC1101_WORCTRL 0x20 // Wake On Radio Control
#define CC1101_FREND1 0x21 // Front End RX Configuration
#define CC1101_FREND0 0x22 // Front End TX Configuration
#define CC1101_FSCAL3 0x23 // Frequency Synthesizer Calibration
#define CC1101_FSCAL2 0x24 // Frequency Synthesizer Calibration
#define CC1101_FSCAL1 0x25 // Frequency Synthesizer Calibration
#define CC1101_FSCAL0 0x26 // Frequency Synthesizer Calibration
#define CC1101_RCCTRL1 0x27 // RC Oscillator Configuration
#define CC1101_RCCTRL0 0x28 // RC Oscillator Configuration
#define CC1101_FSTEST 0x29 // Frequency Synthesizer Calibration Control
#define CC1101_PTEST 0x2A // Production Test
#define CC1101_AGCTEST 0x2B // AGC Test
#define CC1101_TEST2 0x2C // Various Test Settings
#define CC1101_TEST1 0x2D // Various Test Settings
#define CC1101_TEST0 0x2E // Various Test Settings
/**
* Status registers
*/
#define CC1101_PARTNUM 0x30 // Chip ID
#define CC1101_VERSION 0x31 // Chip ID
#define CC1101_FREQEST 0x32 // Frequency Offset Estimate from Demodulator
#define CC1101_LQI 0x33 // Demodulator Estimate for Link Quality
#define CC1101_RSSI 0x34 // Received Signal Strength Indication
#define CC1101_MARCSTATE 0x35 // Main Radio Control State Machine State
#define CC1101_WORTIME1 0x36 // High Byte of WOR Time
#define CC1101_WORTIME0 0x37 // Low Byte of WOR Time
#define CC1101_PKTSTATUS 0x38 // Current GDOx Status and Packet Status
#define CC1101_VCO_VC_DAC 0x39 // Current Setting from PLL Calibration Module
#define CC1101_TXBYTES 0x3A // Underflow and Number of Bytes
#define CC1101_RXBYTES 0x3B // Overflow and Number of Bytes
#define CC1101_RCCTRL1_STATUS 0x3C // Last RC Oscillator Calibration Result
#define CC1101_RCCTRL0_STATUS 0x3D // Last RC Oscillator Calibration Result
typedef struct {
uint16_t Reg;
uint8_t Value;
} RfSetting;
extern spi_device_handle_t gSpiHndl;
void CC1101_SetConfig(const RfSetting *pConfig);
int CC1101_Rx(uint8_t *RxBuf,size_t RxBufLen,uint8_t *pRssi,uint8_t *pLqi);
bool CC1101_Tx(uint8_t *TxData);
bool CC1101_Present(void);
void CC1101_DumpRegs(void);
void CC1101_reset(void);
void CC1101_logState(void);
void CC1101_setRxState(void);
#endif // __CC1101_RADIO_H_

View File

@@ -1,48 +0,0 @@
#include "led.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "proto.h"
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NUM_LEDS 2
const gpio_num_t led_pins[NUM_LEDS] = {LED1, LED2};
TimerHandle_t led_timers[NUM_LEDS] = {0};
void led_timer_callback(TimerHandle_t xTimer) {
int led_index = (int)pvTimerGetTimerID(xTimer);
if (led_index >= 0 && led_index < NUM_LEDS) {
gpio_set_level(led_pins[led_index], 0);
}
}
void init_led() {
gpio_config_t led1 = {};
led1.intr_type = GPIO_INTR_DISABLE;
led1.mode = GPIO_MODE_OUTPUT;
led1.pin_bit_mask = ((1ULL << LED1) | (1ULL << LED2));
led1.pull_down_en = 0;
led1.pull_up_en = 0;
gpio_config(&led1);
for (int i = 0; i < NUM_LEDS; i++) {
led_timers[i] = xTimerCreate("led_timer", pdMS_TO_TICKS(50), pdFALSE, (void *)i, led_timer_callback);
}
}
void led_flash(int nr) {
gpio_set_level(led_pins[nr], 1);
if (nr >= 0 && nr < NUM_LEDS) {
xTimerStart(led_timers[nr], 0);
}
}
void led_set(int nr, bool state) {
gpio_set_level(nr, state);
}

View File

@@ -1,6 +0,0 @@
#pragma once
#include <stdbool.h>
void init_led();
void led_set(int nr, bool state);
void led_flash(int nr);

View File

@@ -1,833 +0,0 @@
// Ported to ESP32-H2 By ATC1441(ATCnetz.de) for OpenEPaperLink at ~08.2023
#include "main.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_ieee802154.h"
#include "esp_log.h"
#include "esp_phy_init.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "led.h"
#include "proto.h"
#include "radio.h"
#include "sdkconfig.h"
#include "second_uart.h"
//#include "soc/lp_uart_reg.h"
#include "soc/uart_struct.h"
#include "utils.h"
#include <esp_mac.h>
#include <math.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "SubGigRadio.h"
static const char *TAG = "MAIN";
const uint8_t channelList[6] = {11, 15, 20, 25, 26, 27};
#define DATATYPE_NOUPDATE 0
#define HW_TYPE 0xC6
#define MAX_PENDING_MACS 250
#define HOUSEKEEPING_INTERVAL 60UL
struct pendingData pendingDataArr[MAX_PENDING_MACS];
// VERSION GOES HERE!
uint16_t version = 0x001d;
#define RAW_PKT_PADDING 2
uint8_t radiotxbuffer[128];
uint8_t radiorxbuffer[128];
static uint32_t housekeepingTimer;
struct blockRequest requestedData = {0}; // holds which data was requested by the tag
uint8_t dstMac[8]; // target for the block transfer
uint16_t dstPan; //
static uint32_t blockStartTimer = 0; // reference that holds when the AP sends the next block
uint32_t nextBlockAttempt = 0; // reference time for when the AP can request a new block from the ESP32
uint8_t seq = 0; // holds current sequence number for transmission
uint8_t blockbuffer[BLOCK_XFER_BUFFER_SIZE + 5]; // block transfer buffer
uint8_t lastAckMac[8] = {0};
// these variables hold the current mac were talking to
#define CONCURRENT_REQUEST_DELAY 1200UL
uint32_t lastBlockRequest = 0;
uint8_t lastBlockMac[8];
uint8_t lastTagReturn[8];
#define NO_SUBGHZ_CHANNEL 255
uint8_t curSubGhzChannel;
uint8_t curChannel = 25;
uint8_t curPower = 10;
uint8_t curPendingData = 0;
uint8_t curNoUpdate = 0;
bool highspeedSerial = false;
void sendXferCompleteAck(uint8_t *dst);
void sendCancelXfer(uint8_t *dst);
void espNotifyAPInfo();
// tools
void addCRC(void *p, uint8_t len) {
uint8_t total = 0;
for (uint8_t c = 1; c < len; c++) {
total += ((uint8_t *) p)[c];
}
((uint8_t *) p)[0] = total;
}
bool checkCRC(void *p, uint8_t len) {
uint8_t total = 0;
for (uint8_t c = 1; c < len; c++) {
total += ((uint8_t *) p)[c];
}
return ((uint8_t *) p)[0] == total;
}
uint8_t getPacketType(void *buffer) {
struct MacFcs *fcs = buffer;
if ((fcs->frameType == 1) && (fcs->destAddrType == 2) && (fcs->srcAddrType == 3) && (fcs->panIdCompressed == 0)) {
// broadcast frame
uint8_t type = ((uint8_t *) buffer)[sizeof(struct MacFrameBcast)];
return type;
} else if ((fcs->frameType == 1) && (fcs->destAddrType == 3) && (fcs->srcAddrType == 3) && (fcs->panIdCompressed == 1)) {
// normal frame
uint8_t type = ((uint8_t *) buffer)[sizeof(struct MacFrameNormal)];
return type;
}
return 0;
}
uint8_t getBlockDataLength() {
uint8_t partNo = 0;
for (uint8_t c = 0; c < BLOCK_MAX_PARTS; c++) {
if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) {
partNo++;
}
}
return partNo;
}
// pendingdata slot stuff
int8_t findSlotForMac(const uint8_t *mac) {
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
if (memcmp(mac, ((uint8_t *) &(pendingDataArr[c].targetMac)), 8) == 0) {
if (pendingDataArr[c].attemptsLeft != 0) {
return c;
}
}
}
return -1;
}
int8_t findFreeSlot() {
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
if (pendingDataArr[c].attemptsLeft == 0) {
return c;
}
}
return -1;
}
int8_t findSlotForVer(const uint8_t *ver) {
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
if (memcmp(ver, ((uint8_t *) &(pendingDataArr[c].availdatainfo.dataVer)), 8) == 0) {
if (pendingDataArr[c].attemptsLeft != 0) return c;
}
}
return -1;
}
void deleteAllPendingDataForVer(const uint8_t *ver) {
int8_t slot = -1;
do {
slot = findSlotForVer(ver);
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
} while (slot != -1);
}
void deleteAllPendingDataForMac(const uint8_t *mac) {
int8_t slot = -1;
do {
slot = findSlotForMac(mac);
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
} while (slot != -1);
}
void countSlots() {
curPendingData = 0;
curNoUpdate = 0;
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
if (pendingDataArr[c].attemptsLeft != 0) {
if (pendingDataArr[c].availdatainfo.dataType != 0) {
curPendingData++;
} else {
curNoUpdate++;
}
}
}
}
// processing serial data
#define ZBS_RX_WAIT_HEADER 0
#define ZBS_RX_WAIT_SDA 1 // send data avail
#define ZBS_RX_WAIT_CANCEL 2 // cancel traffic for mac
#define ZBS_RX_WAIT_SCP 3 // set channel power
#define ZBS_RX_WAIT_BLOCKDATA 4
bool isSame(uint8_t *in1, char *in2, int len) {
bool flag = 1;
for (int i = 0; i < len; i++) {
if (in1[i] != in2[i]) flag = 0;
}
return flag;
}
int blockPosition = 0;
void processSerial(uint8_t lastchar) {
static uint8_t cmdbuffer[4];
static uint8_t RXState = 0;
static uint8_t serialbuffer[48];
static uint8_t *serialbufferp;
static uint8_t bytesRemain = 0;
static uint32_t lastSerial = 0;
static uint32_t blockStartTime = 0;
if ((RXState != ZBS_RX_WAIT_HEADER) && ((getMillis() - lastSerial) > 1000)) {
RXState = ZBS_RX_WAIT_HEADER;
ESP_LOGI(TAG, "UART Timeout");
}
lastSerial = getMillis();
switch (RXState) {
case ZBS_RX_WAIT_HEADER:
// shift characters in
for (uint8_t c = 0; c < 3; c++) {
cmdbuffer[c] = cmdbuffer[c + 1];
}
cmdbuffer[3] = lastchar;
if (isSame(cmdbuffer + 1, ">D>", 3)) {
pr("ACK>");
blockStartTime = getMillis();
ESP_LOGI(TAG, "Starting BlkData, %lu ms after request", blockStartTime - nextBlockAttempt );
blockPosition = 0;
RXState = ZBS_RX_WAIT_BLOCKDATA;
}
if (isSame(cmdbuffer, "SDA>", 4)) {
ESP_LOGI(TAG, "SDA In");
RXState = ZBS_RX_WAIT_SDA;
bytesRemain = sizeof(struct pendingData);
serialbufferp = serialbuffer;
break;
}
if (isSame(cmdbuffer, "CXD>", 4)) {
ESP_LOGI(TAG, "CXD In");
RXState = ZBS_RX_WAIT_CANCEL;
bytesRemain = sizeof(struct pendingData);
serialbufferp = serialbuffer;
break;
}
if (isSame(cmdbuffer, "SCP>", 4)) {
ESP_LOGI(TAG, "SCP In");
RXState = ZBS_RX_WAIT_SCP;
bytesRemain = sizeof(struct espSetChannelPower);
serialbufferp = serialbuffer;
break;
}
if (isSame(cmdbuffer, "NFO?", 4)) {
pr("ACK>");
ESP_LOGI(TAG, "NFO? In");
espNotifyAPInfo();
RXState = ZBS_RX_WAIT_HEADER;
}
if (isSame(cmdbuffer, "RDY?", 4)) {
pr("ACK>");
ESP_LOGI(TAG, "RDY? In");
RXState = ZBS_RX_WAIT_HEADER;
}
if (isSame(cmdbuffer, "RSET", 4)) {
pr("ACK>");
ESP_LOGI(TAG, "RSET In");
delay(100);
// TODO RESET US HERE
RXState = ZBS_RX_WAIT_HEADER;
}
if (isSame(cmdbuffer, "HSPD", 4)) {
pr("ACK>");
ESP_LOGI(TAG, "HSPD In, switching to 2000000");
delay(100);
uart_switch_speed(2000000);
delay(100);
highspeedSerial = true;
pr("ACK>");
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_BLOCKDATA:
blockbuffer[blockPosition++] = 0xAA ^ lastchar;
if (blockPosition >= 4100) {
ESP_LOGI(TAG, "Blockdata fully received in %lu ms, %lu ms after the request", getMillis() - blockStartTime, getMillis() - nextBlockAttempt);
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_SDA:
*serialbufferp = lastchar;
serialbufferp++;
bytesRemain--;
if (bytesRemain == 0) {
if (checkCRC(serialbuffer, sizeof(struct pendingData))) {
struct pendingData *pd = (struct pendingData *) serialbuffer;
int8_t slot = findSlotForMac(pd->targetMac);
if (slot == -1) slot = findFreeSlot();
if (slot != -1) {
memcpy(&(pendingDataArr[slot]), serialbuffer, sizeof(struct pendingData));
pr("ACK>");
} else {
pr("NOQ>");
}
} else {
pr("NOK>");
}
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_CANCEL:
*serialbufferp = lastchar;
serialbufferp++;
bytesRemain--;
if (bytesRemain == 0) {
if (checkCRC(serialbuffer, sizeof(struct pendingData))) {
struct pendingData *pd = (struct pendingData *) serialbuffer;
deleteAllPendingDataForMac((uint8_t *) &pd->targetMac);
pr("ACK>");
} else {
pr("NOK>");
}
RXState = ZBS_RX_WAIT_HEADER;
}
break;
case ZBS_RX_WAIT_SCP:
*serialbufferp = lastchar;
serialbufferp++;
bytesRemain--;
if (bytesRemain == 0) {
if (checkCRC(serialbuffer, sizeof(struct espSetChannelPower))) {
struct espSetChannelPower *scp = (struct espSetChannelPower *) serialbuffer;
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
if(curSubGhzChannel != scp->subghzchannel
&& curSubGhzChannel != NO_SUBGHZ_CHANNEL)
{
curSubGhzChannel = scp->subghzchannel;
ESP_LOGI(TAG,"Set SubGhz channel: %d",curSubGhzChannel);
SubGig_radioSetChannel(scp->subghzchannel);
if(scp->channel == 0) {
// Not setting 802.15.4 channel
goto SCPchannelFound;
}
}
#endif
for (uint8_t c = 0; c < sizeof(channelList); c++) {
if (channelList[c] == scp->channel) goto SCPchannelFound;
}
goto SCPfailed;
SCPchannelFound:
pr("ACK>");
if (curChannel != scp->channel) {
radioSetChannel(scp->channel);
curChannel = scp->channel;
}
curPower = scp->power;
radioSetTxPower(scp->power);
ESP_LOGI(TAG, "Set channel: %d power: %d", curChannel, curPower);
} else {
SCPfailed:
pr("NOK>");
}
RXState = ZBS_RX_WAIT_HEADER;
}
break;
}
}
// sending data to the ESP
void espBlockRequest(const struct blockRequest *br, uint8_t *src) {
struct espBlockRequest *ebr = (struct espBlockRequest *) blockbuffer;
uartTx('R');
uartTx('Q');
uartTx('B');
uartTx('>');
memcpy(&(ebr->ver), &(br->ver), 8);
memcpy(&(ebr->src), src, 8);
ebr->blockId = br->blockId;
addCRC(ebr, sizeof(struct espBlockRequest));
for (uint8_t c = 0; c < sizeof(struct espBlockRequest); c++) {
uartTx(((uint8_t *) ebr)[c]);
}
}
void espNotifyAvailDataReq(const struct AvailDataReq *adr, const uint8_t *src) {
uartTx('A');
uartTx('D');
uartTx('R');
uartTx('>');
struct espAvailDataReq eadr = {0};
memcpy((void *) eadr.src, (void *) src, 8);
memcpy((void *) &eadr.adr, (void *) adr, sizeof(struct AvailDataReq));
addCRC(&eadr, sizeof(struct espAvailDataReq));
for (uint8_t c = 0; c < sizeof(struct espAvailDataReq); c++) {
uartTx(((uint8_t *) &eadr)[c]);
}
}
void espNotifyXferComplete(const uint8_t *src) {
struct espXferComplete exfc;
memcpy(&exfc.src, src, 8);
uartTx('X');
uartTx('F');
uartTx('C');
uartTx('>');
addCRC(&exfc, sizeof(exfc));
for (uint8_t c = 0; c < sizeof(exfc); c++) {
uartTx(((uint8_t *) &exfc)[c]);
}
}
void espNotifyTimeOut(const uint8_t *src) {
struct espXferComplete exfc;
memcpy(&exfc.src, src, 8);
uartTx('X');
uartTx('T');
uartTx('O');
uartTx('>');
addCRC(&exfc, sizeof(exfc));
for (uint8_t c = 0; c < sizeof(exfc); c++) {
uartTx(((uint8_t *) &exfc)[c]);
}
}
void espNotifyAPInfo() {
pr("TYP>%02X", HW_TYPE);
pr("VER>%04X", version);
pr("MAC>%02X%02X", mSelfMac[0], mSelfMac[1]);
pr("%02X%02X", mSelfMac[2], mSelfMac[3]);
pr("%02X%02X", mSelfMac[4], mSelfMac[5]);
pr("%02X%02X", mSelfMac[6], mSelfMac[7]);
pr("ZCH>%02X", curChannel);
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
pr("SCH>%03d",curSubGhzChannel);
#endif
pr("ZPW>%02X", curPower);
countSlots();
pr("PEN>%02X", curPendingData);
pr("NOP>%02X", curNoUpdate);
}
void espNotifyTagReturnData(uint8_t *src, uint8_t len) {
struct tagReturnData *trd = (struct tagReturnData *)(radiorxbuffer + sizeof(struct MacFrameBcast) + 1); // oh how I'd love to pass this as an argument, but sdcc won't let me
struct espTagReturnData *etrd = (struct espTagReturnData *)radiotxbuffer;
if (memcmp((void *) & trd->dataVer, lastTagReturn, 8) == 0) {
return;
} else {
memcpy(lastTagReturn, &trd->dataVer, 8);
}
memcpy(etrd->src, src, 8);
etrd->len = len;
memcpy(&etrd->returnData, trd, len);
addCRC(etrd, len + 10);
uartTx('T');
uartTx('R');
uartTx('D');
uartTx('>');
for (uint8_t c = 0; c < len + 10; c++) {
uartTx(((uint8_t *)etrd)[c]);
}
}
// process data from tag
void processBlockRequest(const uint8_t *buffer, uint8_t forceBlockDownload) {
struct MacFrameNormal *rxHeader = (struct MacFrameNormal *) buffer;
struct blockRequest *blockReq = (struct blockRequest *) (buffer + sizeof(struct MacFrameNormal) + 1);
if (!checkCRC(blockReq, sizeof(struct blockRequest))) return;
// check if we're already talking to this mac
if (memcmp(rxHeader->src, lastBlockMac, 8) == 0) {
lastBlockRequest = getMillis();
} else {
// we weren't talking to this mac, see if there was a transfer in progress from another mac, recently
if ((getMillis() - lastBlockRequest) > CONCURRENT_REQUEST_DELAY) {
// mark this mac as the new current mac we're talking to
memcpy((void *) lastBlockMac, (void *) rxHeader->src, 8);
lastBlockRequest = getMillis();
} else {
// we're talking to another mac, let this mac know we can't accomodate another request right now
pr("BUSY!\n");
sendCancelXfer(rxHeader->src);
return;
}
}
// check if we have data for this mac
if (findSlotForMac(rxHeader->src) == -1) {
// no data for this mac, politely tell it to fuck off
sendCancelXfer(rxHeader->src);
return;
}
bool requestDataDownload = false;
if ((blockReq->blockId != requestedData.blockId) || (blockReq->ver != requestedData.ver)) {
// requested block isn't already in the buffer
requestDataDownload = true;
} else {
// requested block is already in the buffer
if (forceBlockDownload) {
if ((getMillis() - nextBlockAttempt) > 380) {
requestDataDownload = true;
pr("FORCED\n");
} else {
pr("IGNORED\n");
}
}
}
// copy blockrequest into requested data
memcpy(&requestedData, blockReq, sizeof(struct blockRequest));
struct MacFrameNormal *txHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
struct blockRequestAck *blockRequestAck = (struct blockRequestAck *) (radiotxbuffer + sizeof(struct MacFrameNormal) + 2);
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + sizeof(struct blockRequestAck) + RAW_PKT_PADDING;
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_BLOCK_REQUEST_ACK;
if (blockStartTimer == 0) {
if (requestDataDownload) {
if (highspeedSerial == true) {
blockRequestAck->pleaseWaitMs = 140;
} else {
blockRequestAck->pleaseWaitMs = 550;
}
} else {
// block is already in buffer
blockRequestAck->pleaseWaitMs = 30;
}
} else {
blockRequestAck->pleaseWaitMs = 30;
}
blockStartTimer = getMillis() + blockRequestAck->pleaseWaitMs;
memcpy(txHeader->src, mSelfMac, 8);
memcpy(txHeader->dst, rxHeader->src, 8);
txHeader->pan = rxHeader->pan;
txHeader->fcs.frameType = 1;
txHeader->fcs.panIdCompressed = 1;
txHeader->fcs.destAddrType = 3;
txHeader->fcs.srcAddrType = 3;
txHeader->seq = seq++;
addCRC((void *) blockRequestAck, sizeof(struct blockRequestAck));
radioTx(radiotxbuffer);
// save the target for the blockdata
memcpy(dstMac, rxHeader->src, 8);
dstPan = rxHeader->pan;
if (requestDataDownload) {
blockPosition = 0;
espBlockRequest(&requestedData, rxHeader->src);
nextBlockAttempt = getMillis();
}
}
void processAvailDataReq(uint8_t *buffer) {
struct MacFrameBcast *rxHeader = (struct MacFrameBcast *) buffer;
struct AvailDataReq *availDataReq = (struct AvailDataReq *) (buffer + sizeof(struct MacFrameBcast) + 1);
if (!checkCRC(availDataReq, sizeof(struct AvailDataReq))) return;
// prepare tx buffer to send a response
memset(radiotxbuffer, 0, sizeof(struct MacFrameNormal) + sizeof(struct AvailDataInfo) + 2); // 120);
struct MacFrameNormal *txHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
struct AvailDataInfo *availDataInfo = (struct AvailDataInfo *) (radiotxbuffer + sizeof(struct MacFrameNormal) + 2);
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + sizeof(struct AvailDataInfo) + RAW_PKT_PADDING;
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_AVAIL_DATA_INFO;
// check to see if we have data available for this mac
bool haveData = false;
for (uint8_t c = 0; c < MAX_PENDING_MACS; c++) {
if (pendingDataArr[c].attemptsLeft) {
if (memcmp(pendingDataArr[c].targetMac, rxHeader->src, 8) == 0) {
haveData = true;
memcpy((void *) availDataInfo, &(pendingDataArr[c].availdatainfo), sizeof(struct AvailDataInfo));
break;
}
}
}
// couldn't find data for this mac
if (!haveData) availDataInfo->dataType = DATATYPE_NOUPDATE;
memcpy(txHeader->src, mSelfMac, 8);
memcpy(txHeader->dst, rxHeader->src, 8);
txHeader->pan = rxHeader->dstPan;
txHeader->fcs.frameType = 1;
txHeader->fcs.panIdCompressed = 1;
txHeader->fcs.destAddrType = 3;
txHeader->fcs.srcAddrType = 3;
txHeader->seq = seq++;
addCRC(availDataInfo, sizeof(struct AvailDataInfo));
radioTx(radiotxbuffer);
memset(lastAckMac, 0, 8); // reset lastAckMac, so we can record if we've received exactly one ack packet
espNotifyAvailDataReq(availDataReq, rxHeader->src);
}
void processXferComplete(uint8_t *buffer) {
struct MacFrameNormal *rxHeader = (struct MacFrameNormal *) buffer;
sendXferCompleteAck(rxHeader->src);
if (memcmp(lastAckMac, rxHeader->src, 8) != 0) {
memcpy((void *) lastAckMac, (void *) rxHeader->src, 8);
espNotifyXferComplete(rxHeader->src);
int8_t slot = findSlotForMac(rxHeader->src);
if (slot != -1) pendingDataArr[slot].attemptsLeft = 0;
}
}
void processTagReturnData(uint8_t *buffer, uint8_t len) {
struct MacFrameBcast *rxframe = (struct MacFrameBcast *)buffer;
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1);
if (!checkCRC((buffer + sizeof(struct MacFrameBcast) + 1), len - (sizeof(struct MacFrameBcast) + 1))) {
return;
}
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_TAG_RETURN_DATA_ACK;
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
memcpy(frameHeader->src, mSelfMac, 8);
memcpy(frameHeader->dst, rxframe->src, 8);
radiotxbuffer[1] = 0x41; // fast way to set the appropriate bits
radiotxbuffer[2] = 0xCC; // normal frame
frameHeader->seq = seq++;
frameHeader->pan = rxframe->srcPan;
radioTx(radiotxbuffer);
espNotifyTagReturnData(rxframe->src, len - (sizeof(struct MacFrameBcast) + 1));
}
// send block data to the tag
void sendPart(uint8_t partNo) {
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
struct blockPart *blockPart = (struct blockPart *) (radiotxbuffer + sizeof(struct MacFrameNormal) + 2);
memset(radiotxbuffer + 1, 0, sizeof(struct blockPart) + sizeof(struct MacFrameNormal));
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_BLOCK_PART;
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + sizeof(struct blockPart) + BLOCK_PART_DATA_SIZE + 1 + RAW_PKT_PADDING;
memcpy(frameHeader->src, mSelfMac, 8);
memcpy(frameHeader->dst, dstMac, 8);
blockPart->blockId = requestedData.blockId;
blockPart->blockPart = partNo;
memcpy(&(blockPart->data), blockbuffer + (partNo * BLOCK_PART_DATA_SIZE), BLOCK_PART_DATA_SIZE);
addCRC(blockPart, sizeof(struct blockPart) + BLOCK_PART_DATA_SIZE);
frameHeader->fcs.frameType = 1;
frameHeader->fcs.panIdCompressed = 1;
frameHeader->fcs.destAddrType = 3;
frameHeader->fcs.srcAddrType = 3;
frameHeader->seq = seq++;
frameHeader->pan = dstPan;
radioTx(radiotxbuffer);
}
void sendBlockData() {
if (getBlockDataLength() == 0) {
pr("Invalid block request received, 0 parts..\n");
requestedData.requestedParts[0] |= 0x01;
}
pr("Sending parts:");
for (uint8_t c = 0; (c < BLOCK_MAX_PARTS); c++) {
if (c % 10 == 0) pr(" ");
if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) {
pr("X");
} else {
pr(".");
}
}
pr("\n");
uint8_t partNo = 0;
while (partNo < BLOCK_MAX_PARTS) {
for (uint8_t c = 0; (c < BLOCK_MAX_PARTS) && (partNo < BLOCK_MAX_PARTS); c++) {
if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) {
sendPart(c);
partNo++;
}
}
if(dstPan == PROTO_PAN_ID_SUBGHZ) {
// Don't send BLOCK_MAX_PARTS for subgig, it requests what it
// can handle with its limited RAM
break;
}
}
}
void sendXferCompleteAck(uint8_t *dst) {
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
memset(radiotxbuffer + 1, 0, sizeof(struct blockPart) + sizeof(struct MacFrameNormal));
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_XFER_COMPLETE_ACK;
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
memcpy(frameHeader->src, mSelfMac, 8);
memcpy(frameHeader->dst, dst, 8);
frameHeader->fcs.frameType = 1;
frameHeader->fcs.panIdCompressed = 1;
frameHeader->fcs.destAddrType = 3;
frameHeader->fcs.srcAddrType = 3;
frameHeader->seq = seq++;
frameHeader->pan = dstPan;
radioTx(radiotxbuffer);
}
void sendCancelXfer(uint8_t *dst) {
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
memset(radiotxbuffer + 1, 0, sizeof(struct blockPart) + sizeof(struct MacFrameNormal));
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_CANCEL_XFER;
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING;
memcpy(frameHeader->src, mSelfMac, 8);
memcpy(frameHeader->dst, dst, 8);
frameHeader->fcs.frameType = 1;
frameHeader->fcs.panIdCompressed = 1;
frameHeader->fcs.destAddrType = 3;
frameHeader->fcs.srcAddrType = 3;
frameHeader->seq = seq++;
frameHeader->pan = dstPan;
radioTx(radiotxbuffer);
}
void sendPong(void *buf) {
struct MacFrameBcast *rxframe = (struct MacFrameBcast *) buf;
struct MacFrameNormal *frameHeader = (struct MacFrameNormal *) (radiotxbuffer + 1);
radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_PONG;
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
if(rxframe->srcPan == PROTO_PAN_ID_SUBGHZ) {
radiotxbuffer[sizeof(struct MacFrameNormal) + 2] = curSubGhzChannel;
}
else
#endif
radiotxbuffer[sizeof(struct MacFrameNormal) + 2] = curChannel;
radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + 1 + RAW_PKT_PADDING;
memcpy(frameHeader->src, mSelfMac, 8);
memcpy(frameHeader->dst, rxframe->src, 8);
radiotxbuffer[1] = 0x41; // fast way to set the appropriate bits
radiotxbuffer[2] = 0xCC; // normal frame
frameHeader->seq = seq++;
frameHeader->pan = rxframe->srcPan;
radioTx(radiotxbuffer);
}
void app_main(void) {
esp_event_loop_create_default();
init_nvs();
init_led();
init_second_uart();
requestedData.blockId = 0xFF;
// clear the array with pending information
memset(pendingDataArr, 0, sizeof(pendingDataArr));
radio_init(curChannel);
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
if(SubGig_radio_init(curSubGhzChannel)) {
// Ether we don't have a cc1101 or it's not working
curSubGhzChannel = NO_SUBGHZ_CHANNEL;
ESP_LOGI(TAG,"CC1101 NOT detected.");
}
else {
ESP_LOGI(TAG,"CC1101 detected.");
}
#endif
radioSetTxPower(10);
pr("RES>");
pr("RDY>");
ESP_LOGI(TAG, "H2 ready!");
housekeepingTimer = getMillis();
while (1) {
while ((getMillis() - housekeepingTimer) < ((1000 * HOUSEKEEPING_INTERVAL) - 100)) {
int8_t ret = commsRxUnencrypted(radiorxbuffer);
if (ret > 1) {
led_flash(0);
// received a packet, lets see what it is
switch (getPacketType(radiorxbuffer)) {
case PKT_AVAIL_DATA_REQ:
if (ret == 28) {
// old version of the AvailDataReq struct, set all the new fields to zero, so it will pass the CRC
memset(radiorxbuffer + 1 + sizeof(struct MacFrameBcast) + sizeof(struct oldAvailDataReq), 0,
sizeof(struct AvailDataReq) - sizeof(struct oldAvailDataReq) + 2);
processAvailDataReq(radiorxbuffer);
} else if (ret == 40) {
// new version of the AvailDataReq struct
processAvailDataReq(radiorxbuffer);
}
break;
case PKT_BLOCK_REQUEST:
processBlockRequest(radiorxbuffer, 1);
break;
case PKT_BLOCK_PARTIAL_REQUEST:
processBlockRequest(radiorxbuffer, 0);
break;
case PKT_XFER_COMPLETE:
processXferComplete(radiorxbuffer);
break;
case PKT_PING:
sendPong(radiorxbuffer);
break;
case PKT_AVAIL_DATA_SHORTREQ:
// a short AvailDataReq is basically a very short (1 byte payload) packet that requires little preparation on the tx side, for optimal
// battery use bytes of the struct are set 0, so it passes the checksum test, and the ESP32 can detect that no interesting payload is
// sent
if (ret == 18) {
memset(radiorxbuffer + 1 + sizeof(struct MacFrameBcast), 0, sizeof(struct AvailDataReq) + 2);
processAvailDataReq(radiorxbuffer);
}
break;
case PKT_TAG_RETURN_DATA:
processTagReturnData(radiorxbuffer, ret);
break;
default:
ESP_LOGI(TAG, "t=%02X" , getPacketType(radiorxbuffer));
break;
}
} else if (blockStartTimer == 0) {
vTaskDelay(10 / portTICK_PERIOD_MS);
}
uint8_t curr_char;
while (getRxCharSecond(&curr_char)) processSerial(curr_char);
if (blockStartTimer) {
if (getMillis() > blockStartTimer) {
sendBlockData();
blockStartTimer = 0;
}
}
}
memset(&lastTagReturn, 0, 8);
for (uint8_t cCount = 0; cCount < MAX_PENDING_MACS; cCount++) {
if (pendingDataArr[cCount].attemptsLeft == 1) {
if (pendingDataArr[cCount].availdatainfo.dataType != DATATYPE_NOUPDATE) {
espNotifyTimeOut(pendingDataArr[cCount].targetMac);
}
pendingDataArr[cCount].attemptsLeft = 0;
} else if (pendingDataArr[cCount].attemptsLeft > 1) {
pendingDataArr[cCount].attemptsLeft--;
if (pendingDataArr[cCount].availdatainfo.nextCheckIn) pendingDataArr[cCount].availdatainfo.nextCheckIn--;
}
}
housekeepingTimer = getMillis();
}
}

View File

@@ -1 +0,0 @@
#pragma once

View File

@@ -1,194 +0,0 @@
#ifndef _PROTO_H_
#define _PROTO_H_
#include <stdint.h>
#define LED1 22
#define LED2 25
#define PROTO_PAN_ID (0x4447) // PAN ID compression shall be used
#define PROTO_PAN_ID_SUBGHZ (0x1337) // PAN ID compression shall be used
#define RADIO_MAX_PACKET_LEN (125) // useful payload, not including the crc
#define ADDR_MODE_NONE (0)
#define ADDR_MODE_SHORT (2)
#define ADDR_MODE_LONG (3)
#define FRAME_TYPE_BEACON (0)
#define FRAME_TYPE_DATA (1)
#define FRAME_TYPE_ACK (2)
#define FRAME_TYPE_MAC_CMD (3)
#define SHORT_MAC_UNUSED (0x10000000UL) // for radioRxFilterCfg's myShortMac
struct MacFcs {
uint8_t frameType : 3;
uint8_t secure : 1;
uint8_t framePending : 1;
uint8_t ackReqd : 1;
uint8_t panIdCompressed : 1;
uint8_t rfu1 : 1;
uint8_t rfu2 : 2;
uint8_t destAddrType : 2;
uint8_t frameVer : 2;
uint8_t srcAddrType : 2;
} __attribute__((packed, aligned(1)));
struct MacFrameFromMaster {
struct MacFcs fcs;
uint8_t seq;
uint16_t pan;
uint8_t dst[8];
uint16_t from;
} __attribute__((packed, aligned(1)));
struct MacFrameNormal {
struct MacFcs fcs;
uint8_t seq;
uint16_t pan;
uint8_t dst[8];
uint8_t src[8];
} __attribute__((packed, aligned(1)));
struct MacFrameBcast {
struct MacFcs fcs;
uint8_t seq;
uint16_t dstPan;
uint16_t dstAddr;
uint16_t srcPan;
uint8_t src[8];
} __attribute__((packed, aligned(1)));
#define PKT_TAG_RETURN_DATA 0xE1
#define PKT_TAG_RETURN_DATA_ACK 0xE2
#define PKT_AVAIL_DATA_SHORTREQ 0xE3
#define PKT_AVAIL_DATA_REQ 0xE5
#define PKT_AVAIL_DATA_INFO 0xE6
#define PKT_BLOCK_PARTIAL_REQUEST 0xE7
#define PKT_BLOCK_REQUEST_ACK 0xE9
#define PKT_BLOCK_REQUEST 0xE4
#define PKT_BLOCK_PART 0xE8
#define PKT_XFER_COMPLETE 0xEA
#define PKT_XFER_COMPLETE_ACK 0xEB
#define PKT_CANCEL_XFER 0xEC
#define PKT_PING 0xED
#define PKT_PONG 0xEE
struct AvailDataReq {
uint8_t checksum;
uint8_t lastPacketLQI;
int8_t lastPacketRSSI;
int8_t temperature;
uint16_t batteryMv;
uint8_t hwType;
uint8_t wakeupReason;
uint8_t capabilities;
uint16_t tagSoftwareVersion;
uint8_t currentChannel;
uint8_t customMode;
uint8_t reserved[8];
} __attribute__((packed, aligned(1)));
struct oldAvailDataReq {
uint8_t checksum;
uint8_t lastPacketLQI;
int8_t lastPacketRSSI;
int8_t temperature;
uint16_t batteryMv;
uint8_t hwType;
uint8_t wakeupReason;
uint8_t capabilities;
} __attribute__((packed, aligned(1)));
struct AvailDataInfo {
uint8_t checksum;
uint64_t dataVer; // MD5 of potential traffic
uint32_t dataSize;
uint8_t dataType;
uint8_t dataTypeArgument; // extra specification or instruction for the tag (LUT to be used for drawing image)
uint16_t nextCheckIn; // when should the tag check-in again? Measured in minutes
} __attribute__((packed, aligned(1)));
struct pendingData {
struct AvailDataInfo availdatainfo;
uint16_t attemptsLeft;
uint8_t targetMac[8];
} __attribute__((packed, aligned(1)));
struct blockPart {
uint8_t checksum;
uint8_t blockId;
uint8_t blockPart;
uint8_t data[];
} __attribute__((packed, aligned(1)));
struct blockData {
uint16_t size;
uint16_t checksum;
uint8_t data[];
} __attribute__((packed, aligned(1)));
#define TAG_RETURN_DATA_SIZE 90
struct tagReturnData {
uint8_t checksum;
uint8_t partId;
uint64_t dataVer;
uint8_t dataType;
uint8_t data[TAG_RETURN_DATA_SIZE];
} __attribute__((packed, aligned(1)));
#define BLOCK_PART_DATA_SIZE 99
#define BLOCK_MAX_PARTS 42
#define BLOCK_DATA_SIZE 4096UL
#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData)
#define BLOCK_REQ_PARTS_BYTES 6
struct blockRequest {
uint8_t checksum;
uint64_t ver;
uint8_t blockId;
uint8_t type;
uint8_t requestedParts[BLOCK_REQ_PARTS_BYTES];
} __attribute__((packed, aligned(1)));
struct blockRequestAck {
uint8_t checksum;
uint16_t pleaseWaitMs;
} __attribute__((packed, aligned(1)));
struct espBlockRequest {
uint8_t checksum;
uint64_t ver;
uint8_t blockId;
uint8_t src[8];
} __attribute__((packed, aligned(1)));
struct espXferComplete {
uint8_t checksum;
uint8_t src[8];
} __attribute__((packed, aligned(1)));
struct espAvailDataReq {
uint8_t checksum;
uint8_t src[8];
struct AvailDataReq adr;
} __attribute__((packed, aligned(1)));
struct espSetChannelPower {
uint8_t checksum;
uint8_t channel;
uint8_t power;
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
uint8_t subghzchannel;
#endif
} __attribute__((packed, aligned(1)));
struct espTagReturnData {
uint8_t checksum;
uint8_t src[8];
uint8_t len;
struct tagReturnData returnData;
} __attribute__((packed, aligned(1)));
#endif

View File

@@ -1,150 +0,0 @@
#include <stddef.h>
#include "radio.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_ieee802154.h"
#include "esp_log.h"
#include "esp_phy_init.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "led.h"
#include "main.h"
#include "proto.h"
#include "sdkconfig.h"
// if you get an error about soc/lp_uart_reg.h not being found,
// you didn't choose the right build target. :-)
//#include "soc/lp_uart_reg.h"
#include "soc/uart_struct.h"
#include "utils.h"
#include <esp_mac.h>
#include <math.h>
#include <stdarg.h>
#include <string.h>
#include "SubGigRadio.h"
static const char *TAG = "RADIO";
uint8_t mSelfMac[8];
volatile uint8_t isInTransmit = 0;
QueueHandle_t packet_buffer = NULL;
void esp_ieee802154_receive_done(uint8_t *frame, esp_ieee802154_frame_info_t *frame_info) {
ESP_EARLY_LOGI(TAG, "RX %d", frame[0]);
BaseType_t xHigherPriorityTaskWoken;
static uint8_t inner_rxPKT[130];
memcpy(inner_rxPKT, &frame[0], frame[0] + 1);
xQueueSendFromISR(packet_buffer, (void *)&inner_rxPKT, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR_ARG(xHigherPriorityTaskWoken);
esp_ieee802154_receive_sfd_done();
}
void esp_ieee802154_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_error_t error) {
isInTransmit = 0;
ESP_EARLY_LOGE(TAG, "TX Err: %d", error);
}
void esp_ieee802154_transmit_done(const uint8_t *frame, const uint8_t *ack, esp_ieee802154_frame_info_t *ack_frame_info) {
isInTransmit = 0;
ESP_EARLY_LOGI(TAG, "TX %d", frame[0]);
esp_ieee802154_receive_sfd_done();
}
static bool zigbee_is_enabled = false;
void radio_init(uint8_t ch) {
if (packet_buffer == NULL) packet_buffer = xQueueCreate(32, 130);
// this will trigger a "IEEE802154 MAC sleep init failed" when called a second time, but it works
if(zigbee_is_enabled)
{
zigbee_is_enabled = false;
esp_ieee802154_disable();
}
zigbee_is_enabled = true;
esp_ieee802154_enable();
esp_ieee802154_set_channel(ch);
// esp_ieee802154_set_txpower(int8_t power);
esp_ieee802154_set_panid(PROTO_PAN_ID);
esp_ieee802154_set_promiscuous(false);
esp_ieee802154_set_coordinator(false);
esp_ieee802154_set_pending_mode(ESP_IEEE802154_AUTO_PENDING_ZIGBEE);
// esp_ieee802154_set_extended_address needs the MAC in reversed byte order
esp_read_mac(mSelfMac, ESP_MAC_IEEE802154);
uint8_t eui64_rev[8] = {0};
for (int i = 0; i < 8; i++) {
eui64_rev[7 - i] = mSelfMac[i];
}
esp_ieee802154_set_extended_address(eui64_rev);
esp_ieee802154_get_extended_address(mSelfMac);
esp_ieee802154_set_short_address(0xFFFE);
esp_ieee802154_set_rx_when_idle(true);
esp_ieee802154_receive();
led_flash(1);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(0);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(1);
vTaskDelay(100 / portTICK_PERIOD_MS);
led_flash(0);
ESP_LOGI(TAG, "Receiver ready, panId=0x%04x, channel=%d, long=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, short=%04x",
esp_ieee802154_get_panid(), esp_ieee802154_get_channel(),
mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3],
mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7],
esp_ieee802154_get_short_address());
}
// uint32_t lastZbTx = 0;
bool radioTx(uint8_t *packet) {
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
// The subghz driver uses DMA
static DMA_ATTR uint8_t txPKT[130];
#else
static uint8_t txPKT[130];
#endif
led_flash(1);
// while (getMillis() - lastZbTx < 6) {
// }
// lastZbTx = getMillis();
memcpy(txPKT, packet, packet[0]);
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
struct MacFrameNormal *txHeader = (struct MacFrameNormal *) (packet + 1);
if(txHeader->pan == PROTO_PAN_ID_SUBGHZ) {
return SubGig_radioTx(packet);
}
#endif
while (isInTransmit) {
}
isInTransmit = 1;
esp_ieee802154_transmit(txPKT, false);
return true;
}
void radioSetChannel(uint8_t ch) {
radio_init(ch);
}
void radioSetTxPower(uint8_t power) {}
int8_t commsRxUnencrypted(uint8_t *data) {
static uint8_t inner_rxPKT_out[130];
if (xQueueReceive(packet_buffer, (void *)&inner_rxPKT_out, pdMS_TO_TICKS(100)) == pdTRUE) {
memcpy(data, &inner_rxPKT_out[1], inner_rxPKT_out[0] + 1);
return inner_rxPKT_out[0] - 2;
}
#ifdef CONFIG_OEPL_SUBGIG_SUPPORT
if(gSubGigData.Enabled) {
int8_t Ret = SubGig_commsRxUnencrypted(data);
if(Ret > 0) {
return Ret;
}
}
#endif
return 0;
}

View File

@@ -1,20 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define RAW_PKT_PADDING 2
extern uint8_t mSelfMac[8];
void radio_init(uint8_t ch);
bool radioTx(uint8_t *packet);
void radioSetChannel(uint8_t ch);
void radioSetTxPower(uint8_t power);
int8_t commsRxUnencrypted(uint8_t *data);
#ifdef SUBGIG_SUPPORT
void SubGig_radio_init(uint8_t ch);
bool SubGig_radioTx(uint8_t *packet);
void SubGig_radioSetChannel(uint8_t ch);
void SubGig_radioSetTxPower(uint8_t power);
int8_t SubGig_commsRxUnencrypted(uint8_t *data);
#endif

View File

@@ -1,116 +0,0 @@
#include <esp_mac.h>
#include <math.h>
#include <stdarg.h>
#include <string.h>
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_ieee802154.h"
#include "esp_log.h"
#include "esp_phy_init.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "main.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "proto.h"
#include "sdkconfig.h"
#include "soc/uart_struct.h"
//#include "soc/lp_uart_reg.h"
#include "second_uart.h"
//static const char *TAG = "SECOND_UART";
#define BUF_SIZE (1024)
#define RD_BUF_SIZE (BUF_SIZE)
static QueueHandle_t uart0_queue;
#define MAX_BUFF_POS 8000
volatile int curr_buff_pos = 0;
volatile int worked_buff_pos = 0;
volatile uint8_t buff_pos[MAX_BUFF_POS + 5];
static void uart_event_task(void *pvParameters);
void init_second_uart() {
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
ESP_ERROR_CHECK(uart_driver_install(1, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0));
ESP_ERROR_CHECK(uart_param_config(1, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(1, CONFIG_OEPL_HARDWARE_UART_TX, CONFIG_OEPL_HARDWARE_UART_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
xTaskCreate(uart_event_task, "uart_event_task", 16384, NULL, 12, NULL);
}
void uart_switch_speed(int baudrate) {
uart_config_t uart_config = {
.baud_rate = baudrate,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
ESP_ERROR_CHECK(uart_param_config(1, &uart_config));
}
void uartTx(uint8_t data) { uart_write_bytes(1, (const char *) &data, 1); }
bool getRxCharSecond(uint8_t *newChar) {
if (curr_buff_pos != worked_buff_pos) {
*newChar = buff_pos[worked_buff_pos];
worked_buff_pos++;
worked_buff_pos %= MAX_BUFF_POS;
return true;
}
return false;
}
static void uart_event_task(void *pvParameters) {
uart_event_t event;
uint8_t *dtmp = (uint8_t *) malloc(RD_BUF_SIZE);
for (;;) {
if (xQueueReceive(uart0_queue, (void *) &event, (TickType_t) portMAX_DELAY)) {
bzero(dtmp, RD_BUF_SIZE);
switch (event.type) {
case UART_DATA:
uart_read_bytes(1, dtmp, event.size, portMAX_DELAY);
for (int i = 0; i < event.size; i++) {
buff_pos[curr_buff_pos] = dtmp[i];
curr_buff_pos++;
curr_buff_pos %= MAX_BUFF_POS;
}
break;
default:
// ESP_LOGI(TAG, "uart event type: %d", event.type);
break;
}
}
}
free(dtmp);
dtmp = NULL;
vTaskDelete(NULL);
}
void uart_printf(const char *format, ...) {
va_list args;
va_start(args, format);
char buffer[128];
int len = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
if (len > 0) {
uart_write_bytes(1, buffer, len);
}
}

View File

@@ -1,18 +0,0 @@
#pragma once
#include <inttypes.h>
void init_second_uart();
void uart_switch_speed(int baudrate);
void uartTx(uint8_t data);
bool getRxCharSecond(uint8_t *newChar);
void uart_printf(const char *format, ...);
#define pr uart_printf
#define CONFIG_OEPL_HARDWARE_UART_TX 24
#define CONFIG_OEPL_HARDWARE_UART_RX 23

View File

@@ -1,36 +0,0 @@
#include <esp_mac.h>
#include <math.h>
#include <stdarg.h>
#include <string.h>
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_ieee802154.h"
#include "esp_log.h"
#include "esp_phy_init.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "main.h"
#include "proto.h"
#include "sdkconfig.h"
#include "soc/uart_struct.h"
//#include "soc/lp_uart_reg.h"
#include "nvs_flash.h"
void delay(int ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }
uint32_t getMillis() { return (uint32_t) (esp_timer_get_time() / 1000); }
void init_nvs()
{
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
}

View File

@@ -1,5 +0,0 @@
#pragma once
void delay(int ms);
uint32_t getMillis();
void init_nvs();

View File

@@ -1,8 +1,16 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32h2"
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_OEPL_HARDWARE_PROFILE_LILYGO=y
CONFIG_OEPL_SUBGIG_SUPPORT=y
CONFIG_MISO_GPIO=25
CONFIG_SCK_GPIO=3
CONFIG_MOSI_GPIO=11
CONFIG_CSN_GPIO=8
CONFIG_GDO0_GPIO=2
CONFIG_GDO2_GPIO=22

View File

@@ -10,54 +10,6 @@
"/www/painter.js",
"/www/setup.html",
"/www/setup.js",
"/www/upload-demo.html",
"/fonts/weathericons30.vlw",
"/fonts/weathericons70.vlw",
"/fonts/weathericons78.vlw",
"/fonts/calibrib120.vlw",
"/fonts/calibrib150.vlw",
"/fonts/calibrib50.vlw",
"/fonts/calibrib60.vlw",
"/fonts/BellCent10.vlw",
"/tagtypes/00.json",
"/tagtypes/01.json",
"/tagtypes/02.json",
"/tagtypes/05.json",
"/tagtypes/11.json",
"/tagtypes/21.json",
"/tagtypes/22.json",
"/tagtypes/26.json",
"/tagtypes/27.json",
"/tagtypes/2E.json",
"/tagtypes/2F.json",
"/tagtypes/30.json",
"/tagtypes/31.json",
"/tagtypes/32.json",
"/tagtypes/33.json",
"/tagtypes/34.json",
"/tagtypes/35.json",
"/tagtypes/36.json",
"/tagtypes/40.json",
"/tagtypes/41.json",
"/tagtypes/42.json",
"/tagtypes/43.json",
"/tagtypes/55.json",
"/tagtypes/60.json",
"/tagtypes/61.json",
"/tagtypes/62.json",
"/tagtypes/80.json",
"/tagtypes/81.json",
"/tagtypes/82.json",
"/tagtypes/83.json",
"/tagtypes/B0.json",
"/tagtypes/B1.json",
"/tagtypes/B2.json",
"/tagtypes/B3.json",
"/tagtypes/B5.json",
"/tagtypes/BD.json",
"/tagtypes/BE.json",
"/tagtypes/E0.json",
"/tagtypes/E1.json",
"/tagtypes/F0.json"
"/www/upload-demo.html"
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -6,5 +6,6 @@ uint8_t gicToOEPLtype(uint8_t gicType);
bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice);
bool BLE_is_image_pending(uint8_t address[8]);
uint32_t compress_image(uint8_t address[8], uint8_t* buffer, uint32_t max_len);
uint32_t get_ATC_BLE_OEPL_image(uint8_t address[8], uint8_t* buffer, uint32_t max_len, uint8_t* dataType, uint8_t* dataTypeArgument, uint16_t* nextCheckIn);
#endif

View File

@@ -39,6 +39,7 @@ struct imgParam {
uint8_t preloadlut;
uint8_t zlib;
uint8_t g5;
};
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams);

View File

@@ -93,6 +93,7 @@ struct HwType {
uint8_t bpp;
uint8_t shortlut;
uint8_t zlib;
uint8_t g5;
uint16_t highlightColor;
std::vector<Color> colortable;
};

View File

@@ -307,6 +307,7 @@ build_flags =
-D SERIAL_FLASHER_INTERFACE_UART=1
-D SERIAL_FLASHER_BOOT_HOLD_TIME_MS=200
-D SERIAL_FLASHER_RESET_HOLD_TIME_MS=200
-D HAS_SUBGHZ
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<webflasher.cpp>
board_build.flash_mode=qio
@@ -413,6 +414,57 @@ board_build.psram_type=qspi_opi
board_upload.maximum_size = 16777216
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 16MB
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an ESP32-S3 16MB Flash 8MB RAM
; ----------------------------------------------------------------------------------------
[env:ESP32_S3_SIMPLE_AP]
board = esp32-s3-devkitc-1
board_build.partitions = large_spiffs_16MB.csv
build_unflags =
-std=gnu++11
-D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
lib_deps =
${env.lib_deps}
build_flags =
-std=gnu++17
${env.build_flags}
-D HAS_BLE_WRITER
-D CORE_DEBUG_LEVEL=0
-D ARDUINO_USB_CDC_ON_BOOT
-D CONFIG_ESP32S3_SPIRAM_SUPPORT=1
-D CONFIG_SPIRAM_USE_MALLOC=1
-D POWER_NO_SOFT_POWER
-D BOARD_HAS_PSRAM
-D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
-D FLASHER_AP_SS=-1
-D FLASHER_AP_CLK=-1
-D FLASHER_AP_MOSI=-1
-D FLASHER_AP_MISO=-1
-D FLASHER_AP_RESET=41
-D FLASHER_AP_POWER={-1}
-D FLASHER_AP_TEST=-1
-D FLASHER_AP_TXD=2
-D FLASHER_AP_RXD=1
-D FLASHER_DEBUG_TXD=12
-D FLASHER_DEBUG_RXD=11
-D FLASHER_DEBUG_PROG=40
-D FLASHER_LED=38
-D HAS_RGB_LED
-D FLASHER_RGB_LED=48
-D MD5_ENABLED=1
-D SERIAL_FLASHER_INTERFACE_UART=1
-D SERIAL_FLASHER_BOOT_HOLD_TIME_MS=50
-D SERIAL_FLASHER_RESET_HOLD_TIME_MS=100
-D C6_OTA_FLASHING
-D HAS_SUBGHZ
build_src_filter =
+<*>-<usbflasher.cpp>-<swd.cpp>-<webflasher.cpp>
board_build.flash_mode=qio
board_build.arduino.memory_type = qio_opi
board_build.psram_type=qspi_opi
board_upload.maximum_size = 16777216
board_upload.maximum_ram_size = 327680
board_upload.flash_size = 16MB
; ----------------------------------------------------------------------------------------
; !!! this configuration expects an ESP32-S3 16MB Flash 8MB RAM

View File

@@ -64,6 +64,16 @@ uint8_t gicToOEPLtype(uint8_t gicType) {
}
}
struct BleAdvDataStruct {
uint16_t manu_id; // 0x1337 for us
uint8_t version;
uint16_t hw_type;
uint16_t fw_version;
uint16_t capabilities;
uint16_t battery_mv;
uint8_t counter;
} __packed;
bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
@@ -103,10 +113,46 @@ bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice) {
theAdvData.src[7] = manuData[6];
theAdvData.adr.batteryMv = manuData[3] * 100;
theAdvData.adr.lastPacketRSSI = advertisedDevice.getRSSI();
theAdvData.adr.lastPacketLQI = advertisedDevice.getRSSI();
theAdvData.adr.hwType = gicToOEPLtype(manuData[2]);
theAdvData.adr.tagSoftwareVersion = manuData[4] << 8 | manuData[5];
theAdvData.adr.capabilities = 0x00;
processDataReq(&theAdvData, true);
return true;
} else if (manuDatalen >= sizeof(BleAdvDataStruct) && manuData[0] == 0x37 && manuData[1] == 0x13) { // Lets check for a Gicisky E-Paper display
Serial.printf("ATC BLE OEPL Detected\r\n");
struct espAvailDataReq theAdvData;
struct BleAdvDataStruct inAdvData;
memset((uint8_t*)&theAdvData, 0x00, sizeof(espAvailDataReq));
memcpy(&inAdvData, manuData, sizeof(BleAdvDataStruct));
/*Serial.printf("manu_id %04X\r\n", inAdvData.manu_id);
Serial.printf("version %04X\r\n", inAdvData.version);
Serial.printf("hw_type %04X\r\n", inAdvData.hw_type);
Serial.printf("fw_version %04X\r\n", inAdvData.fw_version);
Serial.printf("capabilities %04X\r\n", inAdvData.capabilities);
Serial.printf("battery_mv %u\r\n", inAdvData.battery_mv);
Serial.printf("counter %u\r\n", inAdvData.counter);*/
if (inAdvData.version != 1) {
printf("Version currently not supported!\r\n");
return false;
}
uint8_t macReversed[6];
memcpy(&macReversed, (uint8_t*)advertisedDevice.getAddress().getNative(), 6);
theAdvData.src[0] = macReversed[5];
theAdvData.src[1] = macReversed[4];
theAdvData.src[2] = macReversed[3];
theAdvData.src[3] = macReversed[2];
theAdvData.src[4] = macReversed[1];
theAdvData.src[5] = macReversed[0];
theAdvData.src[6] = manuData[0]; // We use this do find out what type of display we got for compression^^
theAdvData.src[7] = manuData[1];
theAdvData.adr.batteryMv = inAdvData.battery_mv;
theAdvData.adr.lastPacketRSSI = advertisedDevice.getRSSI();
theAdvData.adr.hwType = inAdvData.hw_type & 0xff;
theAdvData.adr.tagSoftwareVersion = inAdvData.fw_version;
theAdvData.adr.capabilities = inAdvData.capabilities & 0xff;
processDataReq(&theAdvData, true);
return true;
}
@@ -133,7 +179,6 @@ bool BLE_filter_add_device(BLEAdvertisedDevice advertisedDevice) {
theAdvData.adr.hwType = ATC_MI_THERMOMETER;
theAdvData.adr.tagSoftwareVersion = 0x00;
theAdvData.adr.capabilities = 0x00;
processDataReq(&theAdvData, true);
Serial.printf("We got an ATC_MiThermometer via BLE\r\n");
return true;
@@ -150,6 +195,14 @@ bool BLE_is_image_pending(uint8_t address[8]) {
return true;
}
}
for (int16_t c = 0; c < tagDB.size(); c++) {
tagRecord* taginfo = tagDB.at(c);
if (taginfo->pendingCount > 0 && taginfo->version == 0 && (taginfo->mac[7] == 0x13) && (taginfo->mac[6] == 0x37)) {
memcpy(address, taginfo->mac, 8);
Serial.printf("ATC BLE OEPL data Waiting\r\n");
return true;
}
}
return false;
}
@@ -374,4 +427,37 @@ uint32_t compress_image(uint8_t address[8], uint8_t* buffer, uint32_t max_len) {
return len_compressed;
}
uint32_t get_ATC_BLE_OEPL_image(uint8_t address[8], uint8_t* buffer, uint32_t max_len, uint8_t* dataType, uint8_t* dataTypeArgument, uint16_t* nextCheckIn) {
uint32_t t = millis();
PendingItem* queueItem = getQueueItem(address, 0);
if (queueItem == nullptr) {
prepareCancelPending(address);
Serial.printf("blockrequest: couldn't find taginfo %02X%02X%02X%02X%02X%02X%02X%02X\r\n", address[7], address[6], address[5], address[4], address[3], address[2], address[1], address[0]);
return 0;
}
if (queueItem->data == nullptr) {
fs::File file = contentFS->open(queueItem->filename);
if (!file) {
Serial.print("No current file. " + String(queueItem->filename) + " Canceling request\r\n");
prepareCancelPending(address);
return 0;
}
queueItem->data = getDataForFile(file);
Serial.println("Reading file " + String(queueItem->filename) + " in " + String(millis() - t) + "ms");
file.close();
}
if (queueItem->len > max_len) {
Serial.print("The upload is too big better cencel it\r\n");
prepareCancelPending(address);
return 0;
}
*dataType = queueItem->pendingdata.availdatainfo.dataType;
*dataTypeArgument = queueItem->pendingdata.availdatainfo.dataTypeArgument;
*nextCheckIn = queueItem->pendingdata.availdatainfo.nextCheckIn;
uint32_t len_compressed = queueItem->len;
memcpy(buffer, queueItem->data, queueItem->len);
Serial.print("Data is prepared Len: " + String(queueItem->len) + "\r\n");
return queueItem->len;
}
#endif

View File

@@ -1,5 +1,6 @@
#ifdef HAS_BLE_WRITER
#include <Arduino.h>
#include <MD5Builder.h>
#include "BLEDevice.h"
#include "ble_filter.h"
@@ -7,12 +8,13 @@
#define INTERVAL_BLE_SCANNING_SECONDS 60
#define INTERVAL_HANDLE_PENDING_SECONDS 10
#define BUFFER_MAX_SIZE_COMPRESSING 100000
#define BUFFER_MAX_SIZE_COMPRESSING 135000
#define BLE_MAIN_STATE_IDLE 0
#define BLE_MAIN_STATE_PREPARE 1
#define BLE_MAIN_STATE_CONNECT 2
#define BLE_MAIN_STATE_UPLOAD 3
#define BLE_MAIN_STATE_ATC_BLE_OEPL_UPLOAD 4
int ble_main_state = BLE_MAIN_STATE_IDLE;
uint32_t last_ble_scan = 0;
@@ -23,9 +25,25 @@ uint32_t last_ble_scan = 0;
#define BLE_UPLOAD_STATE_UPLOAD 5
int BLE_upload_state = BLE_UPLOAD_STATE_INIT;
#define BLE_CMD_ACK_CMD 99
#define BLE_CMD_AVAILDATA 100
#define BLE_CMD_BLK_DATA 101
#define BLE_CMD_ERR_BLKPRT 196
#define BLE_CMD_ACK_BLKPRT 197
#define BLE_CMD_REQ 198
#define BLE_CMD_ACK 199
#define BLE_CMD_ACK_IS_SHOWN 200
#define BLE_CMD_ACK_FW_UPDATED 201
struct AvailDataInfo BLEavaildatainfo = {0};
struct blockRequest BLEblkRequst = {0};
bool BLE_connected = false;
bool BLE_new_notify = false;
static BLEUUID ATC_BLE_OEPL_ServiceUUID((uint16_t)0x1337);
static BLEUUID ATC_BLE_OEPL_CtrlUUID((uint16_t)0x1337);
static BLEUUID gicServiceUUID((uint16_t)0xfef0);
static BLEUUID gicCtrlUUID((uint16_t)0xfef1);
static BLEUUID gicImgUUID((uint16_t)0xfef2);
@@ -33,20 +51,21 @@ static BLEUUID gicImgUUID((uint16_t)0xfef2);
BLERemoteCharacteristic* ctrlChar;
BLERemoteCharacteristic* imgChar;
BLEAdvertisedDevice* myDevice;
BLEClient* pClient;
uint8_t BLE_notify_buffer[255] = {0};
uint8_t BLE_notify_buffer[256] = {0};
uint32_t curr_part = 0;
uint8_t BLE_buff[255];
uint32_t BLE_err_counter = 0;
uint32_t BLE_curr_part = 0;
uint32_t BLE_max_block_parts = 0;
uint8_t BLE_mini_buff[256];
uint32_t BLE_last_notify = 0;
uint32_t BLE_last_pending_check = 0;
uint8_t BLE_curr_address[8] = {0};
uint32_t compressed_len = 0;
uint8_t* buffer;
uint32_t BLE_compressed_len = 0;
uint8_t* BLE_image_buffer;
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
@@ -81,7 +100,13 @@ class MyClientCallback : public BLEClientCallbacks {
}
};
bool BLE_connect(uint8_t addr[8]) {
enum BLE_CONNECTION_TYPE {
BLE_TYPE_GICISKY = 0,
BLE_TYPE_ATC_BLE_OEPL
};
bool BLE_connect(uint8_t addr[8], BLE_CONNECTION_TYPE conn_type) {
BLE_err_counter = 0;
uint8_t temp_Address[] = {addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]};
Serial.printf("BLE Connecting to: %02X:%02X:%02X:%02X:%02X:%02X\r\n", addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
pClient = BLEDevice::createClient();
@@ -92,25 +117,27 @@ bool BLE_connect(uint8_t addr[8]) {
return false;
}
uint32_t timeStart = millis();
while (millis() - timeStart <= 5000) {// We wait for a few seconds as otherwise the connection might not be ready!
while (millis() - timeStart <= 5000) { // We wait for a few seconds as otherwise the connection might not be ready!
delay(100);
}
if (!BLE_connected)
return false;
Serial.printf("BLE starting to get service\r\n");
BLERemoteService* pRemoteService = pClient->getService(gicServiceUUID);
BLERemoteService* pRemoteService = pClient->getService((conn_type == BLE_TYPE_GICISKY) ? gicServiceUUID : ATC_BLE_OEPL_ServiceUUID);
if (pRemoteService == nullptr) {
Serial.printf("BLE Service failed\r\n");
pClient->disconnect();
return false;
}
imgChar = pRemoteService->getCharacteristic(gicImgUUID);
if (imgChar == nullptr) {
Serial.printf("BLE IMG Char failed\r\n");
pClient->disconnect();
return false;
if (conn_type == BLE_TYPE_GICISKY) {
imgChar = pRemoteService->getCharacteristic(gicImgUUID);
if (imgChar == nullptr) {
Serial.printf("BLE IMG Char failed\r\n");
pClient->disconnect();
return false;
}
}
ctrlChar = pRemoteService->getCharacteristic(gicCtrlUUID);
ctrlChar = pRemoteService->getCharacteristic((conn_type == BLE_TYPE_GICISKY) ? gicCtrlUUID : ATC_BLE_OEPL_CtrlUUID);
if (ctrlChar == nullptr) {
Serial.printf("BLE ctrl Char failed\r\n");
pClient->disconnect();
@@ -147,6 +174,49 @@ void BLE_startScan(uint32_t timeout) {
pBLEScan->start(timeout, false);
}
#define BLOCK_DATA_SIZE_BLE 4096
#define BLOCK_PART_DATA_SIZE_BLE 230
uint8_t tempBlockBuffer[BLOCK_DATA_SIZE_BLE + 4];
uint8_t tempPacketBuffer[2 + 3 + BLOCK_PART_DATA_SIZE_BLE];
void ATC_BLE_OEPL_PrepareBlk(uint8_t indexBlockId) {
if (BLE_image_buffer == nullptr) {
return;
}
uint32_t bufferPosition = (BLOCK_DATA_SIZE_BLE * indexBlockId);
uint32_t lenNow = BLOCK_DATA_SIZE_BLE;
uint16_t crcCalc = 0;
if ((BLE_compressed_len - bufferPosition) < BLOCK_DATA_SIZE_BLE)
lenNow = (BLE_compressed_len - bufferPosition);
tempBlockBuffer[0] = lenNow & 0xff;
tempBlockBuffer[1] = (lenNow >> 8) & 0xff;
for (uint16_t c = 0; c < lenNow; c++) {
tempBlockBuffer[4 + c] = BLE_image_buffer[c + bufferPosition];
crcCalc += tempBlockBuffer[4 + c];
}
tempBlockBuffer[2] = crcCalc & 0xff;
tempBlockBuffer[3] = (crcCalc >> 8) & 0xff;
BLE_max_block_parts = (4 + lenNow) / BLOCK_PART_DATA_SIZE_BLE;
if ((4 + lenNow) % BLOCK_PART_DATA_SIZE_BLE)
BLE_max_block_parts++;
Serial.println("Preparing block: " + String(indexBlockId) + " BuffPos: " + String(bufferPosition) + " LenNow: " + String(lenNow) + " MaxBLEparts: " + String(BLE_max_block_parts));
BLE_curr_part = 0;
}
void ATC_BLE_OEPL_SendPart(uint8_t indexBlockId, uint8_t indexPkt) {
uint8_t crcCalc = indexBlockId + indexPkt;
for (uint16_t c = 0; c < BLOCK_PART_DATA_SIZE_BLE; c++) {
tempPacketBuffer[5 + c] = tempBlockBuffer[c + (BLOCK_PART_DATA_SIZE_BLE * indexPkt)];
crcCalc += tempPacketBuffer[5 + c];
}
tempPacketBuffer[0] = 0x00;
tempPacketBuffer[1] = 0x65;
tempPacketBuffer[2] = crcCalc;
tempPacketBuffer[3] = indexBlockId;
tempPacketBuffer[4] = indexPkt;
Serial.println("BLE Sending packet Len " + String(sizeof(tempPacketBuffer)));
ctrlChar->writeValue(tempPacketBuffer, sizeof(tempPacketBuffer), true);
}
void BLETask(void* parameter) {
vTaskDelay(5000 / portTICK_PERIOD_MS);
Serial.println("BLE task started");
@@ -162,25 +232,79 @@ void BLETask(void* parameter) {
}
if (millis() - BLE_last_pending_check >= (INTERVAL_HANDLE_PENDING_SECONDS * 1000)) {
if (BLE_is_image_pending(BLE_curr_address)) {
delay(4000); // We better wait here, since the pending image needs to be created first
Serial.println("BLE Image is pending");
// Here we create the compressed buffer
buffer = (uint8_t*)malloc(BUFFER_MAX_SIZE_COMPRESSING);
if (buffer == nullptr) {
Serial.println("BLE Could not create buffer!");
compressed_len = 0;
} else {
compressed_len = compress_image(BLE_curr_address, buffer, BUFFER_MAX_SIZE_COMPRESSING);
Serial.printf("BLE Compressed Length: %i\r\n", compressed_len);
// then we connect to BLE to send the compressed data
if (compressed_len && BLE_connect(BLE_curr_address)) {
curr_part = 0;
memset(BLE_notify_buffer, 0x00, sizeof(BLE_notify_buffer));
BLE_upload_state = BLE_UPLOAD_STATE_INIT;
ble_main_state = BLE_MAIN_STATE_UPLOAD;
BLE_new_notify = true; // trigger the upload here
Serial.println("BLE Image is pending but we wait a bit");
delay(5000); // We better wait here, since the pending image needs to be created first
if (BLE_curr_address[7] == 0x13 && BLE_curr_address[6] == 0x37) { // This is an ATC BLE OEPL display
// Here we create the compressed buffer
BLE_image_buffer = (uint8_t*)malloc(BUFFER_MAX_SIZE_COMPRESSING);
if (BLE_image_buffer == nullptr) {
Serial.println("BLE Could not create buffer!");
BLE_compressed_len = 0;
} else {
free(buffer);
uint8_t dataType = 0x00;
uint8_t dataTypeArgument = 0x00;
uint16_t nextCheckin = 0x00;
BLE_compressed_len = get_ATC_BLE_OEPL_image(BLE_curr_address, BLE_image_buffer, BUFFER_MAX_SIZE_COMPRESSING, &dataType, &dataTypeArgument, &nextCheckin);
Serial.printf("BLE data Length: %i\r\n", BLE_compressed_len);
// then we connect to BLE to send the compressed data
if (BLE_compressed_len && BLE_connect(BLE_curr_address, BLE_TYPE_ATC_BLE_OEPL)) {
BLE_err_counter = 0;
BLE_curr_part = 0;
memset(BLE_notify_buffer, 0x00, sizeof(BLE_notify_buffer));
uint8_t md5bytes[16];
MD5Builder md5;
md5.begin();
md5.add(BLE_image_buffer, BLE_compressed_len);
md5.calculate();
md5.getBytes(md5bytes);
BLEavaildatainfo.dataType = dataType;
BLEavaildatainfo.dataVer = *((uint64_t*)md5bytes);
BLEavaildatainfo.dataSize = BLE_compressed_len;
BLEavaildatainfo.dataTypeArgument = dataTypeArgument;
BLEavaildatainfo.nextCheckIn = nextCheckin;
BLEavaildatainfo.checksum = 0;
for (uint16_t c = 1; c < sizeof(struct AvailDataInfo); c++) {
BLEavaildatainfo.checksum += (uint8_t)((uint8_t*)&BLEavaildatainfo)[c];
}
BLE_upload_state = BLE_UPLOAD_STATE_INIT;
ble_main_state = BLE_MAIN_STATE_ATC_BLE_OEPL_UPLOAD;
BLE_new_notify = true; // trigger the upload here
} else {
free(BLE_image_buffer);
if (BLE_err_counter++ >= 5) { // 5 Retries for a BLE Connection
struct espXferComplete reportStruct;
memcpy((uint8_t*)&reportStruct.src, BLE_curr_address, 8);
processXferComplete(&reportStruct, true);
}
}
}
} else { // This is a Gicisky display
// Here we create the compressed buffer
BLE_image_buffer = (uint8_t*)malloc(BUFFER_MAX_SIZE_COMPRESSING);
if (BLE_image_buffer == nullptr) {
Serial.println("BLE Could not create buffer!");
BLE_compressed_len = 0;
} else {
BLE_compressed_len = compress_image(BLE_curr_address, BLE_image_buffer, BUFFER_MAX_SIZE_COMPRESSING);
Serial.printf("BLE Compressed Length: %i\r\n", BLE_compressed_len);
// then we connect to BLE to send the compressed data
if (BLE_compressed_len && BLE_connect(BLE_curr_address, BLE_TYPE_GICISKY)) {
BLE_err_counter = 0;
BLE_curr_part = 0;
memset(BLE_notify_buffer, 0x00, sizeof(BLE_notify_buffer));
BLE_upload_state = BLE_UPLOAD_STATE_INIT;
ble_main_state = BLE_MAIN_STATE_UPLOAD;
BLE_new_notify = true; // trigger the upload here
} else {
free(BLE_image_buffer);
if (BLE_err_counter++ >= 5) { // 5 Retries for a BLE Connection
struct espXferComplete reportStruct;
memcpy((uint8_t*)&reportStruct.src, BLE_curr_address, 8);
processXferComplete(&reportStruct, true);
}
}
}
}
BLE_last_pending_check = millis();
@@ -196,25 +320,25 @@ void BLETask(void* parameter) {
switch (BLE_upload_state) {
default:
case BLE_UPLOAD_STATE_INIT:
BLE_buff[0] = 1;
ctrlChar->writeValue(BLE_buff, 1);
BLE_mini_buff[0] = 1;
ctrlChar->writeValue(BLE_mini_buff, 1);
break;
case BLE_UPLOAD_STATE_SIZE:
BLE_buff[0] = 0x02;
BLE_buff[1] = compressed_len & 0xff;
BLE_buff[2] = (compressed_len >> 8) & 0xff;
BLE_buff[3] = (compressed_len >> 16) & 0xff;
BLE_buff[4] = (compressed_len >> 24) & 0xff;
BLE_buff[5] = 0x00;
ctrlChar->writeValue(BLE_buff, 6);
BLE_mini_buff[0] = 0x02;
BLE_mini_buff[1] = BLE_compressed_len & 0xff;
BLE_mini_buff[2] = (BLE_compressed_len >> 8) & 0xff;
BLE_mini_buff[3] = (BLE_compressed_len >> 16) & 0xff;
BLE_mini_buff[4] = (BLE_compressed_len >> 24) & 0xff;
BLE_mini_buff[5] = 0x00;
ctrlChar->writeValue(BLE_mini_buff, 6);
break;
case BLE_UPLOAD_STATE_START:
BLE_buff[0] = 0x03;
ctrlChar->writeValue(BLE_buff, 1);
BLE_mini_buff[0] = 0x03;
ctrlChar->writeValue(BLE_mini_buff, 1);
break;
case BLE_UPLOAD_STATE_UPLOAD:
if (BLE_notify_buffer[2] == 0x08) {
free(buffer);
free(BLE_image_buffer);
pClient->disconnect();
ble_main_state = BLE_MAIN_STATE_IDLE;
BLE_last_pending_check = millis();
@@ -222,35 +346,103 @@ void BLETask(void* parameter) {
struct espXferComplete reportStruct;
memcpy((uint8_t*)&reportStruct.src, BLE_curr_address, 8);
processXferComplete(&reportStruct, true);
curr_part = 0;
BLE_err_counter = 0;
BLE_curr_part = 0;
} else {
uint32_t req_curr_part = (BLE_notify_buffer[6] << 24) | (BLE_notify_buffer[5] << 24) | (BLE_notify_buffer[4] << 24) | BLE_notify_buffer[3];
if (req_curr_part != curr_part) {
Serial.printf("Something went wrong, expected req part: %i but got: %i we better abort here.\r\n", req_curr_part, curr_part);
free(buffer);
if (req_curr_part != BLE_curr_part) {
Serial.printf("Something went wrong, expected req part: %i but got: %i we better abort here.\r\n", req_curr_part, BLE_curr_part);
free(BLE_image_buffer);
pClient->disconnect();
ble_main_state = BLE_MAIN_STATE_IDLE;
BLE_last_pending_check = millis();
}
uint32_t curr_len = 240;
if (compressed_len - (curr_part * 240) < 240)
curr_len = compressed_len - (curr_part * 240);
BLE_buff[0] = curr_part & 0xff;
BLE_buff[1] = (curr_part >> 8) & 0xff;
BLE_buff[2] = (curr_part >> 16) & 0xff;
BLE_buff[3] = (curr_part >> 24) & 0xff;
memcpy((uint8_t*)&BLE_buff[4], (uint8_t*)&buffer[curr_part * 240], curr_len);
imgChar->writeValue(BLE_buff, curr_len + 4);
Serial.printf("BLE sending part: %i\r\n", curr_part);
curr_part++;
if (BLE_compressed_len - (BLE_curr_part * 240) < 240)
curr_len = BLE_compressed_len - (BLE_curr_part * 240);
BLE_mini_buff[0] = BLE_curr_part & 0xff;
BLE_mini_buff[1] = (BLE_curr_part >> 8) & 0xff;
BLE_mini_buff[2] = (BLE_curr_part >> 16) & 0xff;
BLE_mini_buff[3] = (BLE_curr_part >> 24) & 0xff;
memcpy((uint8_t*)&BLE_mini_buff[4], (uint8_t*)&BLE_image_buffer[BLE_curr_part * 240], curr_len);
imgChar->writeValue(BLE_mini_buff, curr_len + 4);
Serial.printf("BLE sending part: %i\r\n", BLE_curr_part);
BLE_curr_part++;
}
break;
}
} else {
if (millis() - BLE_last_notify > 30000) { // Something odd, better reset connection!
Serial.println("BLE err going back to IDLE");
free(buffer);
free(BLE_image_buffer);
pClient->disconnect();
BLE_err_counter = 0;
ble_main_state = BLE_MAIN_STATE_IDLE;
BLE_last_pending_check = millis();
}
}
break;
}
case BLE_MAIN_STATE_ATC_BLE_OEPL_UPLOAD: {
if (BLE_connected && BLE_new_notify) {
BLE_new_notify = false;
BLE_last_notify = millis();
switch (BLE_upload_state) {
default:
case BLE_UPLOAD_STATE_INIT:
BLE_mini_buff[0] = 0x00;
BLE_mini_buff[1] = 0x64;
memcpy((uint8_t*)&BLE_mini_buff[2], &BLEavaildatainfo, sizeof(struct AvailDataInfo));
ctrlChar->writeValue(BLE_mini_buff, sizeof(struct AvailDataInfo) + 2);
BLE_upload_state = BLE_UPLOAD_STATE_UPLOAD;
break;
case BLE_UPLOAD_STATE_UPLOAD: {
uint8_t notifyLen = BLE_notify_buffer[0];
uint16_t notifyCMD = (BLE_notify_buffer[1] << 8) | BLE_notify_buffer[2];
Serial.println("BLE CMD " + String(notifyCMD));
switch (notifyCMD) {
case BLE_CMD_REQ:
if (notifyLen == (sizeof(struct blockRequest) + 2)) {
Serial.println("We got a request for a BLK");
memcpy(&BLEblkRequst, &BLE_notify_buffer[3], sizeof(struct blockRequest));
BLE_curr_part = 0;
ATC_BLE_OEPL_PrepareBlk(BLEblkRequst.blockId);
ATC_BLE_OEPL_SendPart(BLEblkRequst.blockId, BLE_curr_part);
}
break;
case BLE_CMD_ACK_BLKPRT:
BLE_curr_part++;
BLE_err_counter = 0;
case BLE_CMD_ERR_BLKPRT:
if (BLE_curr_part <= BLE_max_block_parts && BLE_err_counter++ < 15) {
ATC_BLE_OEPL_SendPart(BLEblkRequst.blockId, BLE_curr_part);
break;
} // FALLTROUGH!!! We cancel the upload if we land here since we dont have so many parts of a block!
case BLE_CMD_ACK:
case BLE_CMD_ACK_IS_SHOWN:
case BLE_CMD_ACK_FW_UPDATED:
Serial.println("BLE Upload done");
free(BLE_image_buffer);
pClient->disconnect();
ble_main_state = BLE_MAIN_STATE_IDLE;
BLE_last_pending_check = millis();
// Done and the image is refreshing now
struct espXferComplete reportStruct;
memcpy((uint8_t*)&reportStruct.src, BLE_curr_address, 8);
processXferComplete(&reportStruct, true);
BLE_err_counter = 0;
BLE_max_block_parts = 0;
BLE_curr_part = 0;
break;
}
} break;
}
} else {
if (millis() - BLE_last_notify > 30000) { // Something odd, better reset connection!
Serial.println("BLE err going back to IDLE");
free(BLE_image_buffer);
pClient->disconnect();
BLE_err_counter = 0;
ble_main_state = BLE_MAIN_STATE_IDLE;
BLE_last_pending_check = millis();
}

View File

@@ -222,6 +222,15 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) {
} else {
imageParams.zlib = 0;
}
#ifdef SAVE_SPACE
imageParams.g5 = 0;
#else
if (hwdata.g5 != 0 && taginfo->tagSoftwareVersion >= hwdata.g5) {
imageParams.g5 = 1;
} else {
imageParams.g5 = 0;
}
#endif
imageParams.lut = EPD_LUT_NO_REPEATS;
if (taginfo->lut == 2) imageParams.lut = EPD_LUT_FAST_NO_REDS;
@@ -277,9 +286,18 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) {
imageParams.lut = EPD_LUT_DEFAULT;
}
if (imageParams.zlib) {
if (imageParams.bpp == 3) {
imageParams.dataType = DATATYPE_IMG_RAW_3BPP;
Serial.println("datatype: DATATYPE_IMG_RAW_3BPP");
} else if (imageParams.bpp == 4) {
imageParams.dataType = DATATYPE_IMG_RAW_4BPP;
Serial.println("datatype: DATATYPE_IMG_RAW_4BPP");
} else if (imageParams.zlib) {
imageParams.dataType = DATATYPE_IMG_ZLIB;
Serial.println("datatype: DATATYPE_IMG_ZLIB");
} else if (imageParams.g5) {
imageParams.dataType = DATATYPE_IMG_G5;
Serial.println("datatype: DATATYPE_IMG_G5");
} else if (imageParams.hasRed) {
imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
Serial.println("datatype: DATATYPE_IMG_RAW_2BPP");
@@ -437,13 +455,6 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) {
taginfo->nextupdate = 3216153600;
prepareNFCReq(mac, cfgobj["url"].as<const char *>());
break;
case 15: // send gray LUT
taginfo->nextupdate = 3216153600;
prepareLUTreq(mac, cfgobj["bytes"]);
taginfo->hasCustomLUT = true;
break;
#endif
#ifdef CONTENT_BUIENRADAR
@@ -557,9 +568,18 @@ bool updateTagImage(String &filename, const uint8_t *dst, uint16_t nextCheckin,
imageParams.lut = EPD_LUT_DEFAULT;
}
if (imageParams.zlib) {
if (imageParams.bpp == 3) {
imageParams.dataType = DATATYPE_IMG_RAW_3BPP;
Serial.println("datatype: DATATYPE_IMG_RAW_3BPP");
} else if (imageParams.bpp == 4) {
imageParams.dataType = DATATYPE_IMG_RAW_4BPP;
Serial.println("datatype: DATATYPE_IMG_RAW_4BPP");
} else if (imageParams.zlib) {
imageParams.dataType = DATATYPE_IMG_ZLIB;
Serial.println("datatype: DATATYPE_IMG_ZLIB");
} else if (imageParams.g5) {
imageParams.dataType = DATATYPE_IMG_G5;
Serial.println("datatype: DATATYPE_IMG_G5");
} else if (imageParams.hasRed) {
imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
Serial.println("datatype: DATATYPE_IMG_RAW_2BPP");
@@ -681,7 +701,7 @@ void drawTextBox(TFT_eSprite &spr, String &content, int16_t &posx, int16_t &posy
switch (processFontPath(font)) {
case 2: {
// truetype
Serial.println("truetype font not implemented for drawStringBox");
Serial.println("truetype font not implemented for drawTextBox");
} break;
case 3: {
// vlw bitmap font
@@ -990,19 +1010,19 @@ void drawForecast(String &filename, JsonObject &cfgobj, const tagRecord *taginfo
}
if (loc["rain"]) {
if (cfgobj["units"] == "0") {
const int8_t rain = round(daily["precipitation_sum"][dag].as<double>());
if (rain > 0) {
drawString(spr, String(rain) + "mm", dag * column1 + loc["rain"][0].as<int>(), loc["rain"][1], day[2], TC_DATUM, (rain > 10 ? imageParams.highlightColor : TFT_BLACK));
}
} else {
double fRain = daily["precipitation_sum"][dag].as<double>();
fRain = round(fRain*100.0) / 100.0;
if (fRain > 0.0) {
// inch, display if > .01 inches
drawString(spr, String(fRain) + "in", dag * column1 + loc["rain"][0].as<int>(), loc["rain"][1], day[2], TC_DATUM, (fRain > 0.5 ? imageParams.highlightColor : TFT_BLACK));
}
}
if (cfgobj["units"] == "0") {
const int8_t rain = round(daily["precipitation_sum"][dag].as<double>());
if (rain > 0) {
drawString(spr, String(rain) + "mm", dag * column1 + loc["rain"][0].as<int>(), loc["rain"][1], day[2], TC_DATUM, (rain > 10 ? imageParams.highlightColor : TFT_BLACK));
}
} else {
double fRain = daily["precipitation_sum"][dag].as<double>();
fRain = round(fRain * 100.0) / 100.0;
if (fRain > 0.0) {
// inch, display if > .01 inches
drawString(spr, String(fRain) + "in", dag * column1 + loc["rain"][0].as<int>(), loc["rain"][1], day[2], TC_DATUM, (fRain > 0.5 ? imageParams.highlightColor : TFT_BLACK));
}
}
}
drawString(spr, String(tmin) + " ", dag * column1 + day[0].as<int>(), day[4], day[2], TR_DATUM, (tmin < 0 ? imageParams.highlightColor : TFT_BLACK));
@@ -1211,7 +1231,7 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa
int temp = imageParams.height;
imageParams.height = imageParams.width;
imageParams.width = temp;
imageParams.rotatebuffer = 1 - (imageParams.rotatebuffer%2);
imageParams.rotatebuffer = 1 - (imageParams.rotatebuffer % 2);
initSprite(spr, imageParams.width, imageParams.height, imageParams);
} else {
initSprite(spr, imageParams.width, imageParams.height, imageParams);
@@ -1439,18 +1459,32 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa
#ifdef CONTENT_DAYAHEAD
uint16_t getPercentileColor(const double *prices, int numPrices, double price, HwType hwdata) {
double percentile = 100.0;
int colorIndex = 3;
const char *colors[] = {"black", "darkgray", "pink", "red"};
if (hwdata.highlightColor == 3) {
// yellow
colors[2] = "brown";
colors[3] = "yellow";
}
const int numColors = sizeof(colors) / sizeof(colors[0]);
const char *colorsDefault[] = {"black", "darkgray", "pink", "red"};
const double boundariesDefault[] = {40.0, 80.0, 90.0};
const double boundaries[] = {40.0, 80.0, 90.0};
const int numBoundaries = sizeof(boundaries) / sizeof(boundaries[0]);
const char *colors3bpp[] = {"blue", "green", "yellow", "orange", "red"};
const double boundaries3bpp[] = {20.0, 50.0, 70.0, 90.0};
const char **colors;
const double *boundaries;
int numColors, numBoundaries;
if (hwdata.bpp == 3 || hwdata.bpp == 4) {
colors = colors3bpp;
boundaries = boundaries3bpp;
numColors = sizeof(colors3bpp) / sizeof(colors3bpp[0]);
numBoundaries = sizeof(boundaries3bpp) / sizeof(boundaries3bpp[0]);
} else {
colors = colorsDefault;
boundaries = boundariesDefault;
numColors = sizeof(colorsDefault) / sizeof(colorsDefault[0]);
numBoundaries = sizeof(boundariesDefault) / sizeof(boundariesDefault[0]);
if (hwdata.highlightColor == 3) {
colors[2] = "brown";
colors[3] = "yellow";
}
}
int colorIndex = numColors - 1;
for (int i = 0; i < numBoundaries; i++) {
if (price < prices[int(numPrices * boundaries[i] / 100.0)]) {
@@ -1622,7 +1656,7 @@ bool getDayAheadFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo,
spr.fillTriangle(barX + i * barwidth, 15 + arrowY,
barX + i * barwidth + barwidth - 1, 15 + arrowY,
barX + i * barwidth + (barwidth - 1) / 2, 15 + barwidth + arrowY, imageParams.highlightColor);
spr.drawLine(barX + i * barwidth + (barwidth - 1) / 2, 20 + barwidth + arrowY, barX + i * barwidth + (barwidth - 1) / 2, spr.height(), getColor("pink"));
spr.drawLine(barX + i * barwidth + (barwidth - 1) / 2, 20 + barwidth + arrowY, barX + i * barwidth + (barwidth - 1) / 2, spr.height(), TFT_BLACK);
pricenow = price;
}
}
@@ -1809,14 +1843,18 @@ void drawTimestamp(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, im
drawString(spr, "Well done!", spr.width() / 2, 90, "calibrib30.vlw", TC_DATUM, TFT_BLACK);
spr2buffer(spr, filename2, imageParams);
if (imageParams.zlib) imageParams.dataType = DATATYPE_IMG_ZLIB;
if (imageParams.zlib) {
imageParams.dataType = DATATYPE_IMG_ZLIB;
} else if (imageParams.g5) {
imageParams.dataType = DATATYPE_IMG_G5;
}
struct imageDataTypeArgStruct arg = {0};
arg.preloadImage = 1;
arg.specialType = 17; // button 2
arg.lut = 0;
prepareDataAvail(filename2, imageParams.dataType, *((uint8_t *)&arg), taginfo->mac, 5 | 0x8000 );
prepareDataAvail(filename2, imageParams.dataType, *((uint8_t *)&arg), taginfo->mac, 5 | 0x8000);
spr.fillRect(0, 0, spr.width(), spr.height(), TFT_WHITE);
@@ -1828,7 +1866,7 @@ void drawTimestamp(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, im
arg.preloadImage = 1;
arg.specialType = 16; // button 1
arg.lut = 0;
prepareDataAvail(filename2, imageParams.dataType, *((uint8_t *)&arg), taginfo->mac, 5 | 0x8000 );
prepareDataAvail(filename2, imageParams.dataType, *((uint8_t *)&arg), taginfo->mac, 5 | 0x8000);
cfgobj["#init"] = "1";
}
@@ -2165,7 +2203,7 @@ void rotateBuffer(uint8_t rotation, uint8_t &currentOrientation, TFT_eSprite &sp
initSprite(spr, sprCpy.width(), sprCpy.height(), imageParams);
sprCpy.pushToSprite(&spr, 0, 0);
sprCpy.deleteSprite();
imageParams.rotatebuffer = 1 - (imageParams.rotatebuffer%2);
imageParams.rotatebuffer = 1 - (imageParams.rotatebuffer % 2);
}
currentOrientation = rotation;
}
@@ -2217,6 +2255,9 @@ uint16_t getColor(const String &color) {
if (color == "5" || color == "darkgray") return TFT_DARKGREY;
if (color == "6" || color == "pink") return 0xFBCF;
if (color == "7" || color == "brown") return 0x8400;
if (color == "8" || color == "green") return TFT_GREEN;
if (color == "9" || color == "blue") return TFT_BLUE;
if (color == "10" || color == "orange") return 0xFBE0;
uint16_t r, g, b;
if (color.length() == 7 && color[0] == '#' &&
sscanf(color.c_str(), "#%2hx%2hx%2hx", &r, &g, &b) == 3) {
@@ -2315,20 +2356,6 @@ void prepareNFCReq(const uint8_t *dst, const char *url) {
len = 1 + len;
prepareDataAvail(data, len, DATATYPE_NFC_RAW_CONTENT, dst);
}
void prepareLUTreq(const uint8_t *dst, const String &input) {
constexpr const char *delimiters = ", \t";
constexpr const int maxValues = 76;
uint8_t waveform[maxValues];
char *ptr = strtok(const_cast<char *>(input.c_str()), delimiters);
int i = 0;
while (ptr != nullptr && i < maxValues) {
waveform[i++] = static_cast<uint8_t>(strtol(ptr, nullptr, 16));
ptr = strtok(nullptr, delimiters);
}
const size_t waveformLen = sizeof(waveform);
prepareDataAvail(waveform, waveformLen, DATATYPE_CUSTOM_LUT_OTA, dst);
}
#endif
#ifdef CONTENT_TAGCFG

View File

@@ -0,0 +1,203 @@
#ifndef __GROUP5__
#define __GROUP5__
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#ifdef __AVR__
#include <avr/pgmspace.h>
#endif
//
// Group5 1-bit image compression library
// Written by Larry Bank
// Copyright (c) 2024 BitBank Software, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file ./LICENSE.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// ./APL.txt.
//
// The name "Group5" is derived from the CCITT Group4 standard
// This code is based on a lot of the good ideas from CCITT T.6
// for FAX image compression, but modified to work in a very
// constrained environment. The Huffman tables for horizontal
// mode have been replaced with a simple 2-bit flag followed by
// short or long counts of a fixed length. The short codes are
// always 3 bits (run lengths 0-7) and the long codes are the
// number of bits needed to encode the width of the image.
// For example, if a 320 pixel wide image is being compressed,
// the longest horizontal run needed is 320, which requires 9
// bits to encode. The 2 prefix bits have the following meaning:
// 00 = short, short (3+3 bits)
// 01 = short, long (3+N bits)
// 10 = long, short (N+3 bits)
// 11 = long, long (N+N bits)
// The rest of the code works identically to Group4 2D FAX
//
// Caution - this is the maximum number of color changes per line
// The default value is set low to work embedded systems with little RAM
// for font compression, this is plenty since each line of a character should have
// a maximum of 7 color changes
// You can define this in your compiler macros to override the default vlaue
//
#define MAX_IMAGE_FLIPS 640
#ifndef MAX_IMAGE_FLIPS
#ifdef __AVR__
#define MAX_IMAGE_FLIPS 32
#else
#define MAX_IMAGE_FLIPS 512
#endif // __AVR__
#endif
// Horizontal prefix bits
enum {
HORIZ_SHORT_SHORT=0,
HORIZ_SHORT_LONG,
HORIZ_LONG_SHORT,
HORIZ_LONG_LONG
};
// Return code for encoder and decoder
enum {
G5_SUCCESS = 0,
G5_INVALID_PARAMETER,
G5_DECODE_ERROR,
G5_UNSUPPORTED_FEATURE,
G5_ENCODE_COMPLETE,
G5_DECODE_COMPLETE,
G5_NOT_INITIALIZED,
G5_DATA_OVERFLOW,
G5_MAX_FLIPS_EXCEEDED
};
//
// Decoder state
//
typedef struct g5_dec_image_tag
{
int iWidth, iHeight; // image size
int iError;
int y; // last y value drawn
int iVLCSize;
int iHLen; // length of 'long' horizontal codes for this image
int iPitch; // width in bytes of output buffer
uint32_t u32Accum; // fractional scaling accumulator
uint32_t ulBitOff, ulBits; // vlc decode variables
uint8_t *pSrc, *pBuf; // starting & current buffer pointer
int16_t *pCur, *pRef; // current state of current vs reference flips
int16_t CurFlips[MAX_IMAGE_FLIPS];
int16_t RefFlips[MAX_IMAGE_FLIPS];
} G5DECIMAGE;
// Due to unaligned memory causing an exception, we have to do these macros the slow way
#ifdef __AVR__
// assume PROGMEM as the source of data
inline uint32_t TIFFMOTOLONG(uint8_t *p)
{
uint32_t u32 = pgm_read_dword(p);
return __builtin_bswap32(u32);
}
#else
#define TIFFMOTOLONG(p) (((uint32_t)(*p)<<24UL) + ((uint32_t)(*(p+1))<<16UL) + ((uint32_t)(*(p+2))<<8UL) + (uint32_t)(*(p+3)))
#endif // __AVR__
#define TOP_BIT 0x80000000
#define MAX_VALUE 0xffffffff
// Must be a 32-bit target processor
#define REGISTER_WIDTH 32
#define BIGUINT uint32_t
//
// G5 Encoder
//
typedef struct pil_buffered_bits
{
unsigned char *pBuf; // buffer pointer
uint32_t ulBits; // buffered bits
uint32_t ulBitOff; // current bit offset
uint32_t ulDataSize; // available data
} BUFFERED_BITS;
//
// Encoder state
//
typedef struct g5_enc_image_tag
{
int iWidth, iHeight; // image size
int iError;
int y; // last y encoded
int iOutSize;
int iDataSize; // generated output size
uint8_t *pOutBuf;
int16_t *pCur, *pRef; // pointers to swap current and reference lines
BUFFERED_BITS bb;
int16_t CurFlips[MAX_IMAGE_FLIPS];
int16_t RefFlips[MAX_IMAGE_FLIPS];
} G5ENCIMAGE;
// 16-bit marker at the start of a BB_FONT file
// (BitBank FontFile)
#define BB_FONT_MARKER 0xBBFF
// 16-bit marker at the start of a BB_BITMAP file
// (BitBank BitmapFile)
#define BB_BITMAP_MARKER 0xBBBF
// Font info per character (glyph)
typedef struct {
uint16_t bitmapOffset; // Offset to compressed bitmap data for this glyph (starting from the end of the BB_GLYPH[] array)
uint8_t width; // bitmap width in pixels
uint8_t xAdvance; // total width in pixels (bitmap + padding)
uint16_t height; // bitmap height in pixels
int16_t xOffset; // left padding to upper left corner
int16_t yOffset; // padding from baseline to upper left corner (usually negative)
} BB_GLYPH;
// This structure is stored at the beginning of a BB_FONT file
typedef struct {
uint16_t u16Marker; // 16-bit Marker defining a BB_FONT file
uint16_t first; // first char (ASCII value)
uint16_t last; // last char (ASCII value)
uint16_t height; // total height of font
uint32_t rotation; // store this as 32-bits to not have a struct packing problem
BB_GLYPH glyphs[]; // Array of glyphs (one for each char)
} BB_FONT;
// This structure defines the start of a compressed bitmap file
typedef struct {
uint16_t u16Marker; // 16-bit marker defining a BB_BITMAP file
uint16_t width;
uint16_t height;
uint16_t size; // compressed data size (not including this 8-byte header)
} BB_BITMAP;
#ifdef __cplusplus
//
// The G5 classes wrap portable C code which does the actual work
//
class G5ENCODER
{
public:
int init(int iWidth, int iHeight, uint8_t *pOut, int iOutSize);
int encodeLine(uint8_t *pPixels);
int size();
private:
G5ENCIMAGE _g5enc;
};
class G5DECODER
{
public:
int init(int iWidth, int iHeight, uint8_t *pData, int iDataSize);
int decodeLine(uint8_t *pOut);
private:
G5DECIMAGE _g5dec;
};
#endif // __cplusplus
#endif // __GROUP5__

View File

@@ -0,0 +1,338 @@
//
// Group5
// A 1-bpp image decoder
//
// Written by Larry Bank
// Copyright (c) 2024 BitBank Software, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file ./LICENSE.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// ./APL.txt.
#include "Group5.h"
/*
The code tree that follows has: bit_length, decode routine
These codes are for Group 4 (MMR) decoding
01 = vertneg1, 11h = vert1, 20h = horiz, 30h = pass, 12h = vert2
02 = vertneg2, 13h = vert3, 03 = vertneg3, 90h = trash
*/
static const uint8_t code_table[128] =
{0x90, 0, 0x40, 0, /* trash, uncompr mode - codes 0 and 1 */
3, 7, /* V(-3) pos = 2 */
0x13, 7, /* V(3) pos = 3 */
2, 6, 2, 6, /* V(-2) pos = 4,5 */
0x12, 6, 0x12, 6, /* V(2) pos = 6,7 */
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, /* pass pos = 8->F */
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, /* horiz pos = 10->1F */
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
/* V(-1) pos = 20->2F */
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, /* V(1) pos = 30->3F */
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3};
static int g5_decode_init(G5DECIMAGE *pImage, int iWidth, int iHeight, uint8_t *pData, int iDataSize)
{
if (pImage == NULL || iWidth < 1 || iHeight < 1 || pData == NULL || iDataSize < 1)
return G5_INVALID_PARAMETER;
pImage->iVLCSize = iDataSize;
pImage->pSrc = pData;
pImage->ulBitOff = 0;
pImage->y = 0;
pImage->ulBits = TIFFMOTOLONG(pData); // preload the first 32 bits of data
pImage->iWidth = iWidth;
pImage->iHeight = iHeight;
return G5_SUCCESS;
} /* g5_decode_init() */
static void G5DrawLine(G5DECIMAGE *pPage, int16_t *pCurFlips, uint8_t *pOut)
{
int x, len, run;
uint8_t lBit, rBit, *p;
int iStart = 0, xright = pPage->iWidth;
uint8_t *pDest;
iStart = 0;
pDest = pOut;
len = (xright+7)>>3; // number of bytes to generate
for (x=0; x<len; x++) {
pOut[x] = 0xff; // start with white and only draw the black runs
}
x = 0;
while (x < xright) { // while the scaled x is within the window bounds
x = *pCurFlips++; // black starting point
run = *pCurFlips++ - x; // get the black run
x -= iStart;
if (x >= xright || run == 0)
break;
if ((x + run) > 0) { /* If the run is visible, draw it */
if (x < 0) {
run += x; /* draw only visible part of run */
x = 0;
}
if ((x + run) > xright) { /* Don't let it go off right edge */
run = xright - x;
}
/* Draw this run */
lBit = 0xff << (8 - (x & 7));
rBit = 0xff >> ((x + run) & 7);
len = ((x+run)>>3) - (x >> 3);
p = &pDest[x >> 3];
if (len == 0) {
lBit |= rBit;
*p &= lBit;
} else {
*p++ &= lBit;
while (len > 1) {
*p++ = 0;
len--;
}
*p = rBit;
}
} // visible run
} /* while drawing line */
} /* G5DrawLine() */
//
// Initialize internal structures to decode the image
//
static void Decode_Begin(G5DECIMAGE *pPage)
{
int i, xsize;
int16_t *CurFlips, *RefFlips;
xsize = pPage->iWidth;
RefFlips = pPage->RefFlips;
CurFlips = pPage->CurFlips;
/* Seed the current and reference line with XSIZE for V(0) codes */
for (i=0; i<MAX_IMAGE_FLIPS-2; i++) {
RefFlips[i] = xsize;
CurFlips[i] = xsize;
}
/* Prefill both current and reference lines with 7fff to prevent it from
walking off the end if the data gets bunged and the current X is > XSIZE
3-16-94 */
CurFlips[i] = RefFlips[i] = 0x7fff;
CurFlips[i+1] = RefFlips[i+1] = 0x7fff;
pPage->pCur = CurFlips;
pPage->pRef = RefFlips;
pPage->pBuf = pPage->pSrc;
pPage->ulBits = TIFFMOTOLONG(pPage->pSrc); // load 32 bits to start
pPage->ulBitOff = 0;
// Calculate the number of bits needed for a long horizontal code
#ifdef __AVR__
pPage->iHLen = 16 - __builtin_clz(pPage->iWidth);
#else
pPage->iHLen = 32 - __builtin_clz(pPage->iWidth);
#endif
} /* Decode_Begin() */
//
// Decode a single line of G5 data (private function)
//
static int DecodeLine(G5DECIMAGE *pPage)
{
signed int a0, a0_p, b1;
int16_t *pCur, *pRef, *RefFlips, *CurFlips;
int xsize, tot_run=0, tot_run1 = 0;
int32_t sCode;
uint32_t lBits;
uint32_t ulBits, ulBitOff;
uint8_t *pBuf/*, *pBufEnd*/;
uint32_t u32HMask, u32HLen; // horizontal code mask and length
pCur = CurFlips = pPage->pCur;
pRef = RefFlips = pPage->pRef;
ulBits = pPage->ulBits;
ulBitOff = pPage->ulBitOff;
pBuf = pPage->pBuf;
// pBufEnd = &pPage->pSrc[pPage->iVLCSize];
u32HLen = pPage->iHLen;
u32HMask = (1 << u32HLen) - 1;
a0 = -1;
xsize = pPage->iWidth;
while (a0 < xsize) { /* Decode this line */
if (ulBitOff > (REGISTER_WIDTH-8)) { // need at least 7 unused bits
pBuf += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf);
}
if ((int32_t)(ulBits << ulBitOff) < 0) { /* V(0) code is the most frequent case (1 bit) */
a0 = *pRef++;
ulBitOff++; // length = 1 bit
*pCur++ = a0;
} else { /* Slower method for the less frequence codes */
lBits = (ulBits >> ((REGISTER_WIDTH - 8) - ulBitOff)) & 0xfe; /* Only the first 7 bits are useful */
sCode = code_table[lBits]; /* Get the code type as an 8-bit value */
ulBitOff += code_table[lBits+1]; /* Get the code length */
switch (sCode) {
case 1: /* V(-1) */
case 2: /* V(-2) */
case 3: /* V(-3) */
a0 = *pRef - sCode; /* A0 = B1 - x */
*pCur++ = a0;
if (pRef == RefFlips) {
pRef += 2;
}
pRef--;
while (a0 >= *pRef) {
pRef += 2;
}
break;
case 0x11: /* V(1) */
case 0x12: /* V(2) */
case 0x13: /* V(3) */
a0 = *pRef++; /* A0 = B1 */
b1 = a0;
a0 += sCode & 7; /* A0 = B1 + x */
if (b1 != xsize && a0 < xsize) {
while (a0 >= *pRef) {
pRef += 2;
}
}
if (a0 > xsize) {
a0 = xsize;
}
*pCur++ = a0;
break;
case 0x20: /* Horizontal codes */
if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits
pBuf += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf);
}
a0_p = a0;
if (a0 < 0) {
a0_p = 0;
}
lBits = (ulBits >> ((REGISTER_WIDTH - 2) - ulBitOff)) & 0x3; // get 2-bit prefix for code type
// There are 4 possible horizontal cases: short/short, short/long, long/short, long/long
// These are encoded in a 2-bit prefix code, followed by 3 bits for short or N bits for long code
// N is the log base 2 of the image width (e.g. 320 pixels requires 9 bits)
ulBitOff += 2;
switch (lBits) {
case HORIZ_SHORT_SHORT:
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
ulBitOff += 3;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
ulBitOff += 3;
break;
case HORIZ_SHORT_LONG:
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
ulBitOff += 3;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
ulBitOff += u32HLen;
break;
case HORIZ_LONG_SHORT:
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
ulBitOff += u32HLen;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
ulBitOff += 3;
break;
case HORIZ_LONG_LONG:
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
ulBitOff += u32HLen;
if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits
pBuf += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf);
}
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
ulBitOff += u32HLen;
break;
} // switch on lBits
a0 = a0_p + tot_run;
*pCur++ = a0;
a0 += tot_run1;
if (a0 < xsize) {
while (a0 >= *pRef) {
pRef += 2;
}
}
*pCur++ = a0;
break;
case 0x30: /* Pass code */
pRef++; /* A0 = B2, iRef+=2 */
a0 = *pRef++;
break;
default: /* ERROR */
pPage->iError = G5_DECODE_ERROR;
goto pilreadg5z;
} /* switch */
} /* Slow climb */
}
/*--- Convert flips data into run lengths ---*/
*pCur++ = xsize; /* Terminate the line properly */
*pCur++ = xsize;
pilreadg5z:
// Save the current VLC decoder state
pPage->ulBits = ulBits;
pPage->ulBitOff = ulBitOff;
pPage->pBuf = pBuf;
return pPage->iError;
} /* DecodeLine() */
//
// Decompress the VLC data
//
static int g5_decode_line(G5DECIMAGE *pPage, uint8_t *pOut)
{
int rc;
uint8_t *pBufEnd;
int16_t *t1;
if (pPage == NULL || pOut == NULL)
return G5_INVALID_PARAMETER;
if (pPage->y >= pPage->iHeight)
return G5_DECODE_COMPLETE;
pPage->iError = G5_SUCCESS;
if (pPage->y == 0) { // first time through
Decode_Begin(pPage);
}
pBufEnd = &pPage->pSrc[pPage->iVLCSize];
if (pPage->pBuf >= pBufEnd) { // read past the end, error
pPage->iError = G5_DECODE_ERROR;
return G5_DECODE_ERROR;
}
rc = DecodeLine(pPage);
if (rc == G5_SUCCESS) {
// Draw the current line
G5DrawLine(pPage, pPage->pCur, pOut);
/*--- Swap current and reference lines ---*/
t1 = pPage->pRef;
pPage->pRef = pPage->pCur;
pPage->pCur = t1;
pPage->y++;
if (pPage->y >= pPage->iHeight) {
pPage->iError = G5_DECODE_COMPLETE;
}
} else {
pPage->iError = rc;
}
return pPage->iError;
} /* Decode() */

View File

@@ -0,0 +1,305 @@
//
// G5 Encoder
// A 1-bpp image encoding library
//
// Written by Larry Bank
// Copyright (c) 2024 BitBank Software, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file ./LICENSE.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// ./APL.txt.
#include "Group5.h"
/* Number of consecutive 1 bits in a byte from MSB to LSB */
static uint8_t bitcount[256] =
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0-15 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 16-31 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 32-47 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 48-63 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 64-79 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 80-95 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 96-111 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 112-127 */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 128-143 */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 144-159 */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 160-175 */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 176-191 */
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 192-207 */
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 208-223 */
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, /* 224-239 */
4,4,4,4,4,4,4,4,5,5,5,5,6,6,7,8}; /* 240-255 */
/* Table of vertical codes for G5 encoding */
/* code followed by length, starting with v(-3) */
static const uint8_t vtable[14] =
{3,7, /* V(-3) = 0000011 */
3,6, /* V(-2) = 000011 */
3,3, /* V(-1) = 011 */
1,1, /* V(0) = 1 */
2,3, /* V(1) = 010 */
2,6, /* V(2) = 000010 */
2,7}; /* V(3) = 0000010 */
static void G5ENCInsertCode(BUFFERED_BITS *bb, BIGUINT ulCode, int iLen)
{
if ((bb->ulBitOff + iLen) > REGISTER_WIDTH) { // need to write data
bb->ulBits |= (ulCode >> (bb->ulBitOff + iLen - REGISTER_WIDTH)); // partial bits on first word
*(BIGUINT *)bb->pBuf = __builtin_bswap32(bb->ulBits);
bb->pBuf += sizeof(BIGUINT);
bb->ulBits = ulCode << ((REGISTER_WIDTH*2) - (bb->ulBitOff + iLen));
bb->ulBitOff += iLen - REGISTER_WIDTH;
} else {
bb->ulBits |= (ulCode << (REGISTER_WIDTH - bb->ulBitOff - iLen));
bb->ulBitOff += iLen;
}
} /* G5ENCInsertCode() */
//
// Flush any buffered bits to the output
//
static void G5ENCFlushBits(BUFFERED_BITS *bb)
{
while (bb->ulBitOff >= 8)
{
*bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8));
bb->ulBits <<= 8;
bb->ulBitOff -= 8;
}
*bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8));
bb->ulBitOff = 0;
bb->ulBits = 0;
} /* G5ENCFlushBits() */
//
// Initialize the compressor
// This must be called before adding data to the output
//
int g5_encode_init(G5ENCIMAGE *pImage, int iWidth, int iHeight, uint8_t *pOut, int iOutSize)
{
int iError = G5_SUCCESS;
if (pImage == NULL || iHeight <= 0)
return G5_INVALID_PARAMETER;
pImage->iWidth = iWidth; // image size
pImage->iHeight = iHeight;
pImage->pCur = pImage->CurFlips;
pImage->pRef = pImage->RefFlips;
pImage->pOutBuf = pOut; // optional output buffer
pImage->iOutSize = iOutSize; // output buffer pre-allocated size
pImage->iDataSize = 0; // no data yet
pImage->y = 0;
for (int i=0; i<MAX_IMAGE_FLIPS; i++) {
pImage->RefFlips[i] = iWidth;
pImage->CurFlips[i] = iWidth;
}
pImage->bb.pBuf = pImage->pOutBuf;
pImage->bb.ulBits = 0;
pImage->bb.ulBitOff = 0;
pImage->iError = iError;
return iError;
} /* g5_encode_init() */
//
// Internal function to convert uncompressed 1-bit per pixel data
// into the run-end data needed to feed the G5 encoder
//
static int G5ENCEncodeLine(unsigned char *buf, int xsize, int16_t *pDest)
{
int iCount, xborder;
uint8_t i, c;
int8_t cBits;
int iLen;
int16_t x;
int16_t *pLimit = pDest + (MAX_IMAGE_FLIPS-4);
xborder = xsize;
iCount = (xsize + 7) >> 3; /* Number of bytes per line */
cBits = 8;
iLen = 0; /* Current run length */
x = 0;
c = *buf++; /* Get the first byte to start */
iCount--;
while (iCount >=0) {
if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED;
i = bitcount[c]; /* Get the number of consecutive bits */
iLen += i; /* Add this length to total run length */
c <<= i;
cBits -= i; /* Minus the number in a byte */
if (cBits <= 0)
{
iLen += cBits; /* Adjust length */
cBits = 8;
c = *buf++; /* Get another data byte */
iCount--;
continue; /* Keep doing white until color change */
}
c = ~c; /* flip color to count black pixels */
/* Store the white run length */
xborder -= iLen;
if (xborder < 0)
{
iLen += xborder; /* Make sure run length is not past end */
break;
}
x += iLen;
*pDest++ = x;
iLen = 0;
doblack:
i = bitcount[c]; /* Get consecutive bits */
iLen += i; /* Add to total run length */
c <<= i;
cBits -= i;
if (cBits <= 0)
{
iLen += cBits; /* Adjust length */
cBits = 8;
c = *buf++; /* Get another data byte */
c = ~c; /* Flip color to find black */
iCount--;
if (iCount < 0)
break;
goto doblack;
}
/* Store the black run length */
c = ~c; /* Flip color again to find white pixels */
xborder -= iLen;
if (xborder < 0)
{
iLen += xborder; /* Make sure run length is not past end */
break;
}
x += iLen;
*pDest++ = x;
iLen = 0;
} /* while */
x += iLen;
if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED;
*pDest++ = x;
*pDest++ = x; // Store a few more XSIZE to end the line
*pDest++ = x; // so that the compressor doesn't go past
*pDest++ = x; // the end of the line
return G5_SUCCESS;
} /* G5ENCEncodeLine() */
//
// Compress a line of pixels and add it to the output
// the input format is expected to be MSB (most significant bit) first
// for example, pixel 0 is in byte 0 at bit 7 (0x80)
// Returns G5ENC_SUCCESS for each line if all is well and G5ENC_IMAGE_COMPLETE
// for the last line
//
int g5_encode_encodeLine(G5ENCIMAGE *pImage, uint8_t *pPixels)
{
int16_t a0, a0_c, b2, a1;
int dx, run1, run2;
int xsize, iErr, iHighWater;
int iCur, iRef, iLen;
int iHLen; // number of bits for long horizontal codes
int16_t *CurFlips, *RefFlips;
BUFFERED_BITS bb;
if (pImage == NULL || pPixels == NULL)
return G5_INVALID_PARAMETER;
iHighWater = pImage->iOutSize - 32;
iHLen = 32 - __builtin_clz(pImage->iWidth);
memcpy(&bb, &pImage->bb, sizeof(BUFFERED_BITS)); // keep local copy
CurFlips = pImage->pCur;
RefFlips = pImage->pRef;
xsize = pImage->iWidth; /* For performance reasons */
// Convert the incoming line of pixels into run-end data
iErr = G5ENCEncodeLine(pPixels, pImage->iWidth, CurFlips);
if (iErr != G5_SUCCESS) return iErr; // exceeded the maximum number of color changes
/* Encode this line as G5 */
a0 = a0_c = 0;
iCur = iRef = 0;
while (a0 < xsize) {
b2 = RefFlips[iRef+1];
a1 = CurFlips[iCur];
if (b2 < a1) { /* Is b2 to the left of a1? */
/* yes, do pass mode */
a0 = b2;
iRef += 2;
G5ENCInsertCode(&bb, 1, 4); /* Pass code = 0001 */
} else { /* Try vertical and horizontal mode */
dx = RefFlips[iRef] - a1; /* b1 - a1 */
if (dx > 3 || dx < -3) { /* Horizontal mode */
G5ENCInsertCode(&bb, 1, 3); /* Horizontal code = 001 */
run1 = CurFlips[iCur] - a0;
run2 = CurFlips[iCur+1] - CurFlips[iCur];
if (run1 < 8) {
if (run2 < 8) { // short, short
G5ENCInsertCode(&bb, HORIZ_SHORT_SHORT, 2); /* short, short = 00 */
G5ENCInsertCode(&bb, run1, 3);
G5ENCInsertCode(&bb, run2, 3);
} else { // short, long
G5ENCInsertCode(&bb, HORIZ_SHORT_LONG, 2); /* short, long = 01 */
G5ENCInsertCode(&bb, run1, 3);
G5ENCInsertCode(&bb, run2, iHLen);
}
} else { // first run is long
if (run2 < 8) { // long, short
G5ENCInsertCode(&bb, HORIZ_LONG_SHORT, 2); /* long, short = 10 */
G5ENCInsertCode(&bb, run1, iHLen);
G5ENCInsertCode(&bb, run2, 3);
} else { // long, long
G5ENCInsertCode(&bb, HORIZ_LONG_LONG, 2); /* long, long = 11 */
G5ENCInsertCode(&bb, run1, iHLen);
G5ENCInsertCode(&bb, run2, iHLen);
}
}
a0 = CurFlips[iCur+1]; /* a0 = a2 */
if (a0 != xsize) {
iCur += 2; /* Skip two color flips */
while (RefFlips[iRef] != xsize && RefFlips[iRef] <= a0) {
iRef += 2;
}
}
} else { /* Vertical mode */
dx = (dx + 3) * 2; /* Convert to index table */
G5ENCInsertCode(&bb, vtable[dx], vtable[dx+1]);
a0 = a1;
a0_c = 1-a0_c;
if (a0 != xsize) {
if (iRef != 0) {
iRef -= 2;
}
iRef++; /* Skip a color change in cur and ref */
iCur++;
while (RefFlips[iRef] <= a0 && RefFlips[iRef] != xsize) {
iRef += 2;
}
}
} /* vertical mode */
} /* horiz/vert mode */
} /* while x < xsize */
iLen = (int)(bb.pBuf-pImage->pOutBuf);
if (iLen >= iHighWater) { // not enough space
pImage->iError = iErr = G5_DATA_OVERFLOW; // we don't have a better error
return iErr;
}
if (pImage->y == pImage->iHeight-1) { // last line of image
G5ENCFlushBits(&bb); // output the final buffered bits
// wrap up final output
pImage->iDataSize = (int)(bb.pBuf-pImage->pOutBuf);
iErr = G5_ENCODE_COMPLETE;
}
pImage->pCur = RefFlips; // swap current and reference lines
pImage->pRef = CurFlips;
pImage->y++;
memcpy(&pImage->bb, &bb, sizeof(bb));
return iErr;
} /* g5_encode_encodeLine() */
//
// Returns the number of bytes of G5 created by the encoder
//
int g5_encode_getOutSize(G5ENCIMAGE *pImage)
{
int iSize = 0;
if (pImage != NULL)
iSize = pImage->iDataSize;
return iSize;
} /* g5_encode_getOutSize() */

View File

@@ -15,6 +15,12 @@
#include "ips_display.h"
#endif
#include "commstructs.h"
#ifndef SAVE_SPACE
#include "g5/Group5.h"
#include "g5/g5enc.inl"
#endif
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
@@ -80,12 +86,11 @@ uint32_t colorDistance(Color &c1, Color &c2, Error &e1) {
}
void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t buffer_size, bool is_red) {
uint8_t rotate = imageParams.rotate;
long bufw = spr.width(), bufh = spr.height();
if (imageParams.rotatebuffer % 2) {
//turn the image 90 or 270
// turn the image 90 or 270
rotate = (rotate + 3) % 4;
rotate = (rotate + (imageParams.rotatebuffer - 1)) % 4;
bufw = spr.height();
@@ -112,6 +117,7 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t
{12, 5, 14, 6},
{3, 11, 1, 8},
{15, 7, 13, 4}};
size_t bitOffset = 0;
memset(error_bufferold, 0, bufw * sizeof(Error));
for (uint16_t y = 0; y < bufh; y++) {
@@ -134,10 +140,10 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t
if (imageParams.dither == 2) {
// Ordered dithering
uint8_t ditherValue = ditherMatrix[y % 4][x % 4];
error_bufferold[x].r = (ditherValue << 4) - 120; // * 256 / 16 - 128 + 8
error_bufferold[x].g = (ditherValue << 4) - 120;
error_bufferold[x].b = (ditherValue << 4) - 120;
uint8_t ditherValue = ditherMatrix[y % 4][x % 4] << (imageParams.bpp >= 3 ? 2 : 4);
error_bufferold[x].r = ditherValue - (imageParams.bpp >= 3 ? 30 : 120); // * 256 / 16 - 128 + 8
error_bufferold[x].g = ditherValue - (imageParams.bpp >= 3 ? 30 : 120);
error_bufferold[x].b = ditherValue - (imageParams.bpp >= 3 ? 30 : 120);
}
int best_color_index = 0;
@@ -151,24 +157,40 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t
best_color_index = i;
}
}
uint8_t bitIndex = 7 - (x % 8);
uint32_t byteIndex = (y * bufw + x) / 8;
// this looks a bit ugly, but it's performing better than shorter notations
switch (best_color_index) {
case 1:
if (!is_red)
if (imageParams.bpp == 3 || imageParams.bpp == 4) {
size_t byteIndex = bitOffset / 8;
uint8_t bitIndex = bitOffset % 8;
if (bitIndex + imageParams.bpp <= 8) {
buffer[byteIndex] |= best_color_index << (8 - bitIndex - imageParams.bpp);
} else {
uint8_t highPart = best_color_index >> (bitIndex + imageParams.bpp - 8);
uint8_t lowPart = best_color_index & ((1 << (bitIndex + imageParams.bpp - 8)) - 1);
buffer[byteIndex] |= highPart;
buffer[byteIndex + 1] |= lowPart << (8 - (bitIndex + imageParams.bpp - 8));
}
bitOffset += imageParams.bpp;
} else {
uint8_t bitIndex = 7 - (x % 8);
uint32_t byteIndex = (y * bufw + x) / 8;
// this looks a bit ugly, but it's performing better than shorter notations
switch (best_color_index) {
case 1:
if (!is_red)
buffer[byteIndex] |= (1 << bitIndex);
break;
case 2:
imageParams.hasRed = true;
if (is_red)
buffer[byteIndex] |= (1 << bitIndex);
break;
case 3:
imageParams.hasRed = true;
buffer[byteIndex] |= (1 << bitIndex);
break;
case 2:
imageParams.hasRed = true;
if (is_red)
buffer[byteIndex] |= (1 << bitIndex);
break;
case 3:
imageParams.hasRed = true;
buffer[byteIndex] |= (1 << bitIndex);
break;
break;
}
}
if (imageParams.dither == 1) {
@@ -226,7 +248,10 @@ size_t prepareHeader(uint8_t headerbuf[], uint16_t bufw, uint16_t bufh, imgParam
memcpy(headerbuf + (imageParams.rotatebuffer % 2 == 1 ? 3 : 1), &bufw, sizeof(uint16_t));
memcpy(headerbuf + (imageParams.rotatebuffer % 2 == 1 ? 1 : 3), &bufh, sizeof(uint16_t));
if (imageParams.hasRed && imageParams.bpp > 1) {
if (imageParams.bpp == 3 || imageParams.bpp == 4) {
totalbytes = buffer_size * imageParams.bpp + headersize;
headerbuf[5] = imageParams.bpp;
} else if (imageParams.hasRed && imageParams.bpp > 1) {
totalbytes = buffer_size * 2 + headersize;
headerbuf[5] = 2;
} else {
@@ -266,6 +291,32 @@ void rewriteHeader(File &f_out) {
f_out.write(flg);
}
#ifndef SAVE_SPACE
uint8_t *g5Compress(uint16_t width, uint16_t height, uint8_t *buffer, uint16_t buffersize, uint16_t &outBufferSize) {
G5ENCIMAGE g5enc;
int rc;
uint8_t *outbuffer = (uint8_t *)ps_malloc(buffersize + 16384);
if (outbuffer == NULL) {
Serial.println("Failed to allocate the output buffer for the G5 encoder");
return nullptr;
}
rc = g5_encode_init(&g5enc, width, height, outbuffer, buffersize + 16384);
for (int y = 0; y < height && rc == G5_SUCCESS; y++) {
rc = g5_encode_encodeLine(&g5enc, buffer);
buffer += (width / 8);
}
if (rc == G5_ENCODE_COMPLETE) {
outBufferSize = g5_encode_getOutSize(&g5enc);
} else {
printf("Encode failed! rc=%d\n", rc);
free(outbuffer);
return nullptr;
}
return outbuffer;
}
#endif
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
long t = millis();
@@ -274,11 +325,11 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
if (fileout == "direct") {
if (tftOverride == false) {
TFT_eSprite spr2 = TFT_eSprite(&tft2);
#ifdef ST7735_NANO_TLSR
#ifdef ST7735_NANO_TLSR
tft2.setRotation(1);
#else
#else
tft2.setRotation(YellowSense == 1 ? 1 : 3);
#endif
#endif
spr2.createSprite(spr.width(), spr.height());
spr2.setColorDepth(spr.getColorDepth());
@@ -287,20 +338,19 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
size_t dataSize = spr.width() * spr.height() * (spr.getColorDepth() / 8);
memcpy(spriteData2, spriteData, dataSize);
#ifdef HAS_LILYGO_TPANEL
if (spr.getColorDepth() == 16)
{
long dy = spr.height();
long dx = spr.width();
uint16_t* data = static_cast<uint16_t*>(const_cast<void*>(spriteData2));
#ifdef HAS_LILYGO_TPANEL
if (spr.getColorDepth() == 16) {
long dy = spr.height();
long dx = spr.width();
gfx->draw16bitRGBBitmap(0, 0, (uint16_t *)spriteData2, dx, dy);
spr2.deleteSprite();
}
#else
uint16_t *data = static_cast<uint16_t *>(const_cast<void *>(spriteData2));
gfx->draw16bitRGBBitmap(0, 0, (uint16_t *)spriteData2, dx, dy);
spr2.deleteSprite();
}
#else
spr2.pushSprite(0, 0);
#endif
#endif
}
return;
}
@@ -319,6 +369,7 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
#else
uint8_t *buffer = (uint8_t *)malloc(buffer_size);
imageParams.zlib = 0;
imageParams.g5 = 0;
#endif
if (!buffer) {
Serial.println("Failed to allocate buffer");
@@ -359,6 +410,70 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
free(comp);
rewriteHeader(f_out);
#ifndef SAVE_SPACE
} else if (imageParams.g5) {
// handling for G5-compressed image data
uint8_t headerbuf[6];
prepareHeader(headerbuf, bufw, bufh, imageParams, buffer_size);
f_out.write(headerbuf, sizeof(headerbuf));
uint16_t height = imageParams.height; // spr.height();
uint16_t width = imageParams.width;
spr.width();
if (imageParams.hasRed && imageParams.bpp > 1) {
uint8_t *newbuffer = (uint8_t *)ps_realloc(buffer, 2 * buffer_size);
if (newbuffer == NULL) {
Serial.println("Failed to allocate larger buffer for 2bpp G5");
free(buffer);
f_out.close();
xSemaphoreGive(fsMutex);
return;
}
buffer = newbuffer;
spr2color(spr, imageParams, buffer + buffer_size, buffer_size, true);
buffer_size *= 2;
// double the height, to do two layers sequentially
if (imageParams.rotatebuffer % 2) {
width *= 2;
} else {
height *= 2;
}
}
uint16_t outbufferSize = 0;
uint8_t *outBuffer;
bool compressionSuccessful = true;
if (imageParams.rotatebuffer % 2) {
outBuffer = g5Compress(height, width, buffer, buffer_size, outbufferSize);
} else {
outBuffer = g5Compress(width, height, buffer, buffer_size, outbufferSize);
}
if (outBuffer == NULL) {
Serial.println("Failed to compress G5");
compressionSuccessful = false;
} else {
printf("Compressed %d to %d bytes\n", buffer_size, outbufferSize);
if (outbufferSize > buffer_size) {
printf("That wasn't very useful, falling back to raw\n");
compressionSuccessful = false;
f_out.seek(0);
} else {
f_out.write(outBuffer, outbufferSize);
}
free(outBuffer);
}
if (!compressionSuccessful) {
// if we failed to compress the image, or the resulting image was larger than a raw file, fallback
imageParams.g5 = false;
if (imageParams.hasRed && imageParams.bpp > 1) {
imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
} else {
imageParams.dataType = DATATYPE_IMG_RAW_1BPP;
}
f_out.write(buffer, buffer_size);
}
#endif
} else {
f_out.write(buffer, buffer_size);
if (imageParams.hasRed && imageParams.bpp > 1) {
@@ -370,6 +485,24 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
free(buffer);
} break;
case 3:
case 4: {
long bufw = spr.width(), bufh = spr.height();
size_t buffer_size = (bufw * bufh) / 8 * imageParams.bpp;
uint8_t *buffer = (uint8_t *)ps_malloc(buffer_size);
if (!buffer) {
Serial.println("Failed to allocate buffer");
util::printLargestFreeBlock();
f_out.close();
xSemaphoreGive(fsMutex);
return;
}
spr2color(spr, imageParams, buffer, buffer_size, false);
f_out.write(buffer, buffer_size);
free(buffer);
} break;
case 16: {
size_t spriteDataSize = (spr.getColorDepth() == 1) ? (spr.width() * spr.height() / 8) : ((spr.getColorDepth() == 8) ? (spr.width() * spr.height()) : ((spr.getColorDepth() == 16) ? (spr.width() * spr.height() * 2) : 0));
f_out.write((const uint8_t *)spr.getPointer(), spriteDataSize);

View File

@@ -273,7 +273,9 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
case DATATYPE_IMG_DIFF:
case DATATYPE_IMG_ZLIB:
case DATATYPE_IMG_RAW_1BPP:
case DATATYPE_IMG_RAW_2BPP: {
case DATATYPE_IMG_RAW_2BPP:
case DATATYPE_IMG_G5:
case DATATYPE_IMG_RAW_3BPP: {
char hexmac[17];
mac2hex(pending->targetMac, hexmac);
String filename = "/current/" + String(hexmac) + "_" + String(millis() % 1000000) + ".pending";
@@ -445,7 +447,7 @@ void processXferComplete(struct espXferComplete* xfc, bool local) {
contentFS->remove(dst_path);
}
if (contentFS->exists(queueItem->filename)) {
if (config.preview && (queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_ZLIB)) {
if (config.preview && (queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_3BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_G5 || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_ZLIB)) {
contentFS->rename(queueItem->filename, String(dst_path));
} else {
if (queueItem->pendingdata.availdatainfo.dataType != DATATYPE_FW_UPDATE) contentFS->remove(queueItem->filename);
@@ -966,7 +968,7 @@ bool queueDataAvail(struct pendingData* pending, bool local) {
}
newPending.len = taginfo->len;
if ((pending->availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP || pending->availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || pending->availdatainfo.dataType == DATATYPE_IMG_ZLIB) && (pending->availdatainfo.dataTypeArgument & 0xF8) == 0x00) {
if ((pending->availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP || pending->availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || pending->availdatainfo.dataType == DATATYPE_IMG_RAW_3BPP || pending->availdatainfo.dataType == DATATYPE_IMG_ZLIB || pending->availdatainfo.dataType == DATATYPE_IMG_G5) && (pending->availdatainfo.dataTypeArgument & 0xF8) == 0x00) {
// in case of an image (no preload), remove already queued images
pendingQueue.erase(std::remove_if(pendingQueue.begin(), pendingQueue.end(),
[pending](const PendingItem& item) {

View File

@@ -334,9 +334,9 @@ void initAPconfig() {
config.sleepTime2 = APconfig.containsKey("sleeptime2") ? APconfig["sleeptime2"] : 0;
config.ble = APconfig.containsKey("ble") ? APconfig["ble"] : 0;
config.discovery = APconfig.containsKey("discovery") ? APconfig["discovery"] : 0;
#ifdef BLE_ONLY
#ifdef BLE_ONLY
config.ble = true;
#endif
#endif
// default wifi power 8.5 dbM
// see https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiGeneric.h#L111
config.wifiPower = APconfig.containsKey("wifipower") ? APconfig["wifipower"] : 34;
@@ -395,6 +395,7 @@ HwType getHwType(const uint8_t id) {
filter["bpp"] = true;
filter["shortlut"] = true;
filter["zlib_compression"] = true;
filter["g5_compression"] = true;
filter["highlight_color"] = true;
filter["colortable"] = true;
StaticJsonDocument<1000> doc;
@@ -416,6 +417,11 @@ HwType getHwType(const uint8_t id) {
} else {
hwType.zlib = 0;
}
if (doc.containsKey("g5_compression")) {
hwType.g5 = strtol(doc["g5_compression"], nullptr, 16);
} else {
hwType.g5 = 0;
}
hwType.highlightColor = doc.containsKey("highlight_color") ? doc["highlight_color"].as<uint16_t>() : 2;
JsonObject colorTable = doc["colortable"];
for (auto kv : colorTable) {

View File

@@ -593,21 +593,6 @@
}
]
},
{
"id": 15,
"name": "Send custom LUT",
"desc": "EXPERIMENTAL. Don't use. YOU RISK DAMAGING YOUR SCREEN.",
"capabilities": 4,
"properties": [ "savespace" ],
"param": [
{
"key": "bytes",
"name": "bytes",
"desc": "76 bytes, formatted as 0x00,0x00,...",
"type": "text"
}
]
},
{
"id": 17,
"name": "Send Command",

View File

@@ -425,7 +425,7 @@
const canvas = $('#previewimg');
canvas.style.display = 'block';
fetch(path)
fetch(path + "?r=" + Math.random())
.then(response => response.arrayBuffer())
.then(buffer => {
@@ -433,6 +433,16 @@
if (tagTypes[hwtype].zlib > 0 && targetDiv.dataset.ver >= tagTypes[hwtype].zlib) {
data = window.opener.processZlib(data);
}
if (data.length > 0 && tagTypes[hwtype].g5 > 0 && targetDiv.dataset.ver >= tagTypes[hwtype].g5) {
const headerSize = data[0];
let bufw = (data[2] << 8) | data[1];
let bufh = (data[4] << 8) | data[3];
if ((bufw == tagTypes[hwtype].width || bufw == tagTypes[hwtype].height) && (bufh == tagTypes[hwtype].width || bufh == tagTypes[hwtype].height) && (data[5] <= 3)) {
// valid header for g5 compression
if (data[5] == 2) bufh *= 2;
data = window.opener.processG5(data.subarray(headerSize), bufw, bufh);
}
}
[canvas.width, canvas.height] = [tagTypes[hwtype].width, tagTypes[hwtype].height] || [0, 0];
if (tagTypes[hwtype].rotatebuffer%2) [canvas.width, canvas.height] = [canvas.height, canvas.width];
@@ -452,7 +462,27 @@
imageData.data[i * 4 + 2] = is16Bit ? (rgb & 0x1F) << 3 : ((rgb & 0x03) << 6) * 1.3;
imageData.data[i * 4 + 3] = 255;
}
} else if (tagTypes[hwtype].bpp == 3) {
const colorTable = tagTypes[hwtype].colortable;
let pixelIndex = 0;
for (let i = 0; i < data.length; i += 3) {
for (let j = 0; j < 8; j++) {
let bitPos = j * 3;
let bytePos = Math.floor(bitPos / 8);
let bitOffset = bitPos % 8;
let pixelValue = (data[i + bytePos] >> (5 - bitOffset)) & 0x07;
if (bitOffset > 5) {
pixelValue = ((data[i + bytePos] & (0xFF >> bitOffset)) << (bitOffset - 5)) |
(data[i + bytePos + 1] >> (13 - bitOffset));
}
imageData.data[pixelIndex * 4] = colorTable[pixelValue][0];
imageData.data[pixelIndex * 4 + 1] = colorTable[pixelValue][1];
imageData.data[pixelIndex * 4 + 2] = colorTable[pixelValue][2];
imageData.data[pixelIndex * 4 + 3] = 255;
pixelIndex++;
}
}
} else {
const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0;

View File

@@ -0,0 +1,377 @@
//
// Group5
// A 1-bpp image decoder
//
// Written by Larry Bank
// Copyright (c) 2024 BitBank Software, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file ./LICENSE.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// ./APL.txt.
// Converted from C to Javascript by Nic Limper
// Define constants
const MAX_IMAGE_FLIPS = 640;
// Horizontal prefix bits
const HORIZ_SHORT_SHORT = 0;
const HORIZ_SHORT_LONG = 1;
const HORIZ_LONG_SHORT = 2;
const HORIZ_LONG_LONG = 3;
// Return code for encoder and decoder
const G5_SUCCESS = 0;
const G5_INVALID_PARAMETER = 1;
const G5_DECODE_ERROR = 2;
const G5_UNSUPPORTED_FEATURE = 3;
const G5_ENCODE_COMPLETE = 4;
const G5_DECODE_COMPLETE = 5;
const G5_NOT_INITIALIZED = 6;
const G5_DATA_OVERFLOW = 7;
const G5_MAX_FLIPS_EXCEEDED = 8;
// Utility function equivalent to the TIFFMOTOLONG macro
function TIFFMOTOLONG(p, ix) {
let value = 0;
if (ix < p.length) value |= p[ix] << 24;
if (ix + 1 < p.length) value |= p[ix + 1] << 16;
if (ix + 2 < p.length) value |= p[ix + 2] << 8;
if (ix + 3 < p.length) value |= p[ix + 3];
return value;
}
// Constants for bit manipulation
const REGISTER_WIDTH = 32; // Must align with a 32-bit system in C++
/*
The code tree that follows has: bit_length, decode routine
These codes are for Group 4 (MMR) decoding
01 = vertneg1, 11h = vert1, 20h = horiz, 30h = pass, 12h = vert2
02 = vertneg2, 13h = vert3, 03 = vertneg3, 90h = trash
*/
const code_table = [
0x90, 0, 0x40, 0, // trash, uncompressed mode - codes 0 and 1
3, 7, // V(-3) pos = 2
0x13, 7, // V(3) pos = 3
2, 6, 2, 6, // V(-2) pos = 4,5
0x12, 6, 0x12, 6, // V(2) pos = 6,7
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, // pass pos = 8->F
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, // horiz pos = 10->1F
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, // V(-1) pos = 20->2F
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, // V(1) pos = 30->3F
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3
];
class G5DECIMAGE {
constructor() {
this.iWidth = 0;
this.iHeight = 0;
this.iError = 0;
this.y = 0;
this.iVLCSize = 0;
this.iHLen = 0;
this.iPitch = 0;
this.u32Accum = 0;
this.ulBitOff = 0;
this.ulBits = 0;
this.pSrc = null; // Input buffer
this.pBuf = null; // Current buffer index
this.pBufIndex = 0;
this.pCur = new Int16Array(MAX_IMAGE_FLIPS); // Current state
this.pRef = new Int16Array(MAX_IMAGE_FLIPS); // Reference state
}
}
//static int g5_decode_init(G5DECIMAGE *pImage, int iWidth, int iHeight, uint8_t *pData, int iDataSize)
function g5_decode_init(pImage, iWidth, iHeight, pData, iDataSize) {
if (
pImage == null ||
iWidth < 1 ||
iHeight < 1 ||
pData == null ||
iDataSize < 1
) {
return G5_INVALID_PARAMETER;
}
pImage.iVLCSize = iDataSize;
pImage.pSrc = pData;
pImage.ulBitOff = 0;
pImage.y = 0;
pImage.ulBits = TIFFMOTOLONG(pData, 0); // Preload the first 32 bits of data
pImage.iWidth = iWidth;
pImage.iHeight = iHeight;
return G5_SUCCESS;
}
//static void G5DrawLine(G5DECIMAGE *pPage, int16_t *pCurFlips, uint8_t *pOut)
function G5DrawLine(pPage, pCurFlips, pOut) {
const xright = pPage.iWidth;
let pCurIndex = 0;
// Initialize output to white (0xff)
const len = (xright + 7) >> 3; // Number of bytes to generate
pOut.fill(0xff, 0, len);
let x = 0;
while (x < xright) { // While the scaled x is within the window bounds
const startX = pCurFlips[pCurIndex++]; // Black starting point
const run = pCurFlips[pCurIndex++] - startX; // Get the black run
if (startX >= xright || run <= 0) break;
// Calculate visible run
let visibleX = Math.max(0, startX);
let visibleRun = Math.min(xright, startX + run) - visibleX;
if (visibleRun > 0) {
const startByte = visibleX >> 3;
const endByte = (visibleX + visibleRun) >> 3;
const lBit = (0xff << (8 - (visibleX & 7))) & 0xff; // Left bitmask based on the starting x position
const rBit = 0xff >> ((visibleX + visibleRun) & 7); // Right bitmask based on the ending x position
if (endByte == startByte) {
// If the run fits in a single byte, combine left and right bit masks
pOut[startByte] &= (lBit | rBit);
} else {
// Mask the left-most byte
pOut[startByte] &= lBit;
// Set intermediate bytes to 0
for (let i = startByte + 1; i < endByte; i++) {
pOut[i] = 0x00;
}
// Mask the right-most byte if it's not fully aligned
pOut[endByte] &= rBit;
}
}
}
}
// Initialize internal structures to decode the image
//
function Decode_Begin(pPage) {
const xsize = pPage.iWidth;
// Seed the current and reference lines with xsize for V(0) codes
for (let i = 0; i < MAX_IMAGE_FLIPS - 2; i++) {
pPage.pRef[i] = xsize;
pPage.pCur[i] = xsize;
}
// Prefill both current and reference lines with 0x7fff to prevent walking off the end
// if the data gets bunged and the current X is > XSIZE
pPage.pCur[MAX_IMAGE_FLIPS - 2] = pPage.pRef[MAX_IMAGE_FLIPS - 2] = 0x7fff;
pPage.pCur[MAX_IMAGE_FLIPS - 1] = pPage.pRef[MAX_IMAGE_FLIPS - 1] = 0x7fff;
pPage.pBuf = pPage.pSrc; // Start buffer
pPage.pBufIndex = 0;
// Load 32 bits to start (use a helper function to interpret bytes as a 32-bit integer)
pPage.ulBits = TIFFMOTOLONG(pPage.pSrc, 0);
pPage.ulBitOff = 0;
// Calculate the number of bits needed for a long horizontal code
pPage.iHLen = 32 - Math.clz32(pPage.iWidth); // clz32 counts leading zeroes in JavaScript
}
// Decode a single line of G5 data
//
function DecodeLine(pPage) {
let a0 = -1;
let a0_p, b1;
let pCurIndex = 0, pRefIndex = 0;
const pCur = pPage.pCur;
const pRef = pPage.pRef;
let ulBits = pPage.ulBits;
let ulBitOff = pPage.ulBitOff;
let pBufIndex = pPage.pBufIndex;
const pBuf = pPage.pBuf;
const xsize = pPage.iWidth;
const u32HLen = pPage.iHLen;
const u32HMask = (1 << u32HLen) - 1;
let tot_run, tot_run1;
while (a0 < xsize) {
if (ulBitOff > (REGISTER_WIDTH - 8)) {
pBufIndex += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
}
if (((ulBits << ulBitOff) & 0x80000000) !== 0) {
a0 = pRef[pRefIndex++];
pCur[pCurIndex++] = a0;
ulBitOff++;
} else {
const lBits = (ulBits >> (REGISTER_WIDTH - 8 - ulBitOff)) & 0xfe;
const sCode = code_table[lBits];
ulBitOff += code_table[lBits + 1];
switch (sCode) {
case 1: case 2: case 3: // V(-1), V(-2), V(-3)
a0 = pRef[pRefIndex] - sCode; // A0 = B1 - x
pCur[pCurIndex++] = a0;
if (pRefIndex == 0) {
pRefIndex += 2;
}
pRefIndex--;
while (a0 >= pRef[pRefIndex]) {
pRefIndex += 2;
}
break;
case 0x11: case 0x12: case 0x13: // V(1), V(2), V(3)
a0 = pRef[pRefIndex++];
b1 = a0;
a0 += sCode & 7;
if (b1 !== xsize && a0 < xsize) {
while (a0 >= pRef[pRefIndex]) {
pRefIndex += 2;
}
}
if (a0 > xsize) {
a0 = xsize;
}
pCur[pCurIndex++] = a0;
break;
case 0x20: // Horizontal codes
if (ulBitOff > (REGISTER_WIDTH - 16)) {
pBufIndex += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
}
a0_p = Math.max(0, a0);
const lBits = (ulBits >> ((REGISTER_WIDTH - 2) - ulBitOff)) & 0x3;
ulBitOff += 2;
switch (lBits) {
case HORIZ_SHORT_SHORT:
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
ulBitOff += 3;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
ulBitOff += 3;
break;
case HORIZ_SHORT_LONG:
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
ulBitOff += 3;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
ulBitOff += u32HLen;
break;
case HORIZ_LONG_SHORT:
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
ulBitOff += u32HLen;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
ulBitOff += 3;
break;
case HORIZ_LONG_LONG:
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
ulBitOff += u32HLen;
if (ulBitOff > (REGISTER_WIDTH - 16)) {
pBufIndex += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
}
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
ulBitOff += u32HLen;
break;
}
a0 = a0_p + tot_run;
pCur[pCurIndex++] = a0;
a0 += tot_run1;
if (a0 < xsize) {
while (a0 >= pRef[pRefIndex]) {
pRefIndex += 2;
}
}
pCur[pCurIndex++] = a0;
break;
case 0x30: // Pass code
pRefIndex++;
a0 = pRef[pRefIndex++];
break;
default: // ERROR
pPage.iError = G5_DECODE_ERROR;
return pPage.iError;
break;
}
}
}
pCur[pCurIndex++] = xsize;
pCur[pCurIndex++] = xsize;
pPage.ulBits = ulBits;
pPage.ulBitOff = ulBitOff;
pPage.pBufIndex = pBufIndex;
return pPage.iError;
}
function processG5(data, width, height) {
try {
let decoder = new G5DECIMAGE();
let initResult = g5_decode_init(decoder, width, height, data, data.length);
if (initResult !== G5_SUCCESS) {
throw new Error("Initialization failed with code: " + initResult);
}
Decode_Begin(decoder);
let outputBuffer = new Uint8Array(height * ((width + 7) >> 3)); // Adjust for byte alignment
for (let y = 0; y < height; y++) {
let lineBuffer = outputBuffer.subarray(y * ((width + 7) >> 3), (y + 1) * ((width + 7) >> 3));
decoder.y = y;
let decodeResult = DecodeLine(decoder);
if (decodeResult !== G5_SUCCESS) {
console.log("Decoding error on line " + y + ": " + decoder.iError);
}
G5DrawLine(decoder, decoder.pCur, lineBuffer);
const temp = decoder.pRef;
decoder.pRef = decoder.pCur;
decoder.pCur = temp;
}
return outputBuffer;
} catch (error) {
console.error("Error during G5 decoding:", error.message);
return null;
}
}

View File

@@ -6,8 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>Open EPaper Link Access Point</title>
<script src="main.js" defer></script>
<link rel="stylesheet" href="main.css" type="text/css" />
<script src="main.js?2.74" defer></script>
<script src="g5decoder.js?2.74"></script>
<link rel="stylesheet" href="main.css?2.74" type="text/css" />
<!--<link rel="icon" type="image/vnd.icon" href="favicon.ico">-->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" />

View File

@@ -1207,6 +1207,16 @@ function drawCanvas(buffer, canvas, hwtype, tagmac, doRotate) {
if (data.length > 0 && tagTypes[hwtype].zlib > 0 && $('#tag' + tagmac).dataset.ver >= tagTypes[hwtype].zlib) {
data = processZlib(data);
}
if (data.length > 0 && tagTypes[hwtype].g5 > 0 && $('#tag' + tagmac).dataset.ver >= tagTypes[hwtype].g5) {
const headerSize = data[0];
let bufw = (data[2] << 8) | data[1];
let bufh = (data[4] << 8) | data[3];
if ((bufw == tagTypes[hwtype].width || bufw == tagTypes[hwtype].height) && (bufh == tagTypes[hwtype].width || bufh == tagTypes[hwtype].height) && (data[5] <= 3)) {
// valid header for g5 compression
if (data[5] == 2) bufh *= 2;
data = processG5(data.subarray(headerSize), bufw, bufh);
}
}
[canvas.width, canvas.height] = [tagTypes[hwtype].width, tagTypes[hwtype].height] || [0, 0];
if (tagTypes[hwtype].rotatebuffer % 2) [canvas.width, canvas.height] = [canvas.height, canvas.width];
@@ -1243,6 +1253,28 @@ function drawCanvas(buffer, canvas, hwtype, tagmac, doRotate) {
imageData.data[i * 4 + 3] = 255;
}
} else if (tagTypes[hwtype].bpp == 3) {
const colorTable = tagTypes[hwtype].colortable;
let pixelIndex = 0;
for (let i = 0; i < data.length; i += 3) {
for (let j = 0; j < 8; j++) {
let bitPos = j * 3;
let bytePos = Math.floor(bitPos / 8);
let bitOffset = bitPos % 8;
let pixelValue = (data[i + bytePos] >> (5 - bitOffset)) & 0x07;
if (bitOffset > 5) {
pixelValue = ((data[i + bytePos] & (0xFF >> bitOffset)) << (bitOffset - 5)) |
(data[i + bytePos + 1] >> (13 - bitOffset));
}
imageData.data[pixelIndex * 4] = colorTable[pixelValue][0];
imageData.data[pixelIndex * 4 + 1] = colorTable[pixelValue][1];
imageData.data[pixelIndex * 4 + 2] = colorTable[pixelValue][2];
imageData.data[pixelIndex * 4 + 3] = 255;
pixelIndex++;
}
}
} else {
const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0;
@@ -1499,6 +1531,7 @@ async function getTagtype(hwtype) {
contentids: Object.values(jsonData.contentids ?? []),
options: Object.values(jsonData.options ?? []),
zlib: parseInt(jsonData.zlib_compression || "0", 16),
g5: parseInt(jsonData.g5_compression || "0", 16),
shortlut: parseInt(jsonData.shortlut),
busy: false,
usetemplate: parseInt(jsonData.usetemplate || "0", 10)
@@ -1746,7 +1779,7 @@ function populateAPCard(msg) {
function populateAPInfo(apip) {
let apid = apip.replace(/\./g, "-");
fetch('sysinfo')
fetch('http://' + apip + '/sysinfo')
.then(response => {
if (response.status != 200) {
$('#ap' + apid + ' .apswversion').innerHTML = "Error fetching sysinfo: " + response.status;
@@ -1895,11 +1928,12 @@ function showPreview(previewWindow, element) {
console.log('refresh ' + element.mac);
previewWindow.pending = element.pending;
previewWindow.hash = "";
let cachetag = Date.now();
if (element.isexternal && element.contentMode == 12) {
imageSrc = 'http://' + tagDB[element.mac].apip + '/getdata?mac=' + element.mac + '&md5=0000000000000000';
imageSrc = 'http://' + tagDB[element.mac].apip + '/getdata?mac=' + element.mac + '&md5=0000000000000000&c=' + cachetag;
} else {
imageSrc = '/getdata?mac=' + element.mac + '&md5=0000000000000000';
imageSrc = '/getdata?mac=' + element.mac + '&md5=0000000000000000&c=' + cachetag;
}
} else if (element.hash != previewWindow.hash) {
@@ -1924,4 +1958,4 @@ function showPreview(previewWindow, element) {
console.error('fetch preview image error:', error);
});
}
}
}

View File

@@ -56,16 +56,17 @@ export async function initUpdate() {
.then(([sdata, rdata]) => {
if (sdata.env) {
let matchtest = '';
if (sdata.buildversion != filesystemversion && filesystemversion != "custom" && sdata.buildversion != "custom") matchtest = " <- not matching!"
print(`env: ${sdata.env}`);
print(`current env: ${sdata.env}`);
print(`build date: ${formatEpoch(sdata.buildtime)}`);
print(`esp32 version: ${sdata.buildversion}`);
print(`filesystem version: ${filesystemversion}` + matchtest);
print(`filesystem version: ${filesystemversion}`);
print(`psram size: ${sdata.psramsize}`);
print(`flash size: ${sdata.flashsize}`);
print("--------------------------", "gray");
env = sdata.env;
env = apConfig.env || sdata.env;
if (sdata.env != env) {
print(`Warning: you selected a build environment ${env} which is\ndifferent than the currently used ${sdata.env}.\nOnly update the firmware with a mismatched build environment if\nyou know what you're doing.`, "yellow");
}
currentVer = sdata.buildversion;
currentBuildtime = sdata.buildtime;
if (sdata.rollback) $("#rollbackOption").style.display = 'block';
@@ -96,9 +97,9 @@ export async function initUpdate() {
} else {
const release = releaseDetails[0];
if (release?.tag_name) {
if (release.tag_name == currentVer) {
if (normalizeVersion(release.tag_name) === normalizeVersion(currentVer)) {
easyupdate.innerHTML = `Version ${currentVer}. You are up to date`;
} else if (release.date < formatEpoch(currentBuildtime)) {
} else if (release.date < formatEpoch(currentBuildtime - 30 * 60)) {
easyupdate.innerHTML = `Your version is newer than the latest release date.<br>Are you the developer? :-)`;
} else {
easyupdate.innerHTML = `An update from version ${currentVer} to version ${release.tag_name} is available.<button onclick="otamodule.updateAll('${release.bin_url}','${release.file_url}','${release.tag_name}')">Update now!</button>`;
@@ -637,3 +638,7 @@ async function fetchAndCheckTagtypes(cleanup) {
print("Error: " + error, "red");
}
}
function normalizeVersion(version) {
return version.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,996 @@
:040000000200833245
:01000B0032C2
:0100130032BA
:01001B0032B2
:0100230032AA
:03002B00024A4343
:01003300329A
:01003B003292
:0300430002500563
:03004B0002496007
:01005300327A
:01005B003272
:01006300326A
:01006B003262
:01007300325A
:03007B00025B4CD9
:0300FA0002007E83
:03007E0002050672
:2000FD00E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FBA6
:20011D00004380040022AF8290F090E4F090F091F08F061FEE60767E0090F95AE0FDBD024E
:20013D000BEE9062AF93FDBDC800405990F95AE0FDBD010BEE9062AF93FDBDC8005046EEA9
:20015D0024AFFCE43462FD8C828D83E493F582C007C006C005C004121C5EE582D004D0057E
:20017D00D006D007601F90F090E0FB90F0BFE0FAC3EB9A501090F090EAF08C828D83E4930B
:20019D00FC90F091F00EBE0C005086808C90F091E0FFE06013BFC800400890F95A7402F030
:2001BD00800690F95A7401F090F091E0F5822290F0EDE0FEA3E0FFC3742C9E74019F40099F
:2001DD0090F0E3E0700330015E300A0920020690F0C6E0700930031E90F0C7E0601890FC37
:2001FD0063E0FFBFFF02800B7824E4F28F821227818003123CFA121E2AAD82AE83AFF09064
:20021D00F092EDF0EEA3F0EFA3F0ED4E602D90F0EDE4F0A3F010010090F0E3E06002D2013A
:20023D0090F0E3E4F08014121F15AD82AE83AFF090F092EDF0EEA3F0EFA3F01218BE90F037
:20025D0092E0FDA3E0FEA3E0FFED4E700990F0E1E4F0A3F08059740F2DFAE43EFB8F048AD6
:20027D00828B838CF012628FFAA312628FFB90F0E1EAF0EBA3F0740D2DFAE43EFB8F048A1C
:20029D00828B838CF012628F60168D828E838FF0123156E582701890F0E1E4F0A3F0800F3E
:2002BD0074C0C0E07462C0E0125A4615811581121909AE82AF8390F0EDE0FCA3E0FD90F01A
:2002DD00EDEE2CF0EF3DA3F0BE580CBF020990F0C5E06003C2002290FA5BE0600E90FA5BDB
:2002FD00E4F0900064E4F5F002180990F0E1E0FEA3E0FF4E603BEF30E71F8E048F055305E0
:20031D007F7877ECF2ED08F2E408F208F29003E8E4F5F0125F410218097877EEF2EF08F2E4
:20033D00E408F208F290EA60E4F5F0125F41021809121909AE82AF837877EEF2EF08F2E41E
:20035D0008F208F29003E8E4F5F0125F4102180990F0CEE0FF600D8F82121C5EE58290F94C
:20037D0059F0800C758202120123E58290F959F090F959E0700920030690F0C7E0701430E4
:20039D000A0920020690F0C6E0700890F0E4E0FFBF233690FC63E0FFBFFF02801A758202EB
:2003BD00123E9AE582702290FC63E0FF7824E4F28F82122781801290F0E4E0FFBF2300403B
:2003DD0005123E3B8003123DF890F959E0604890F0E4E4F090F0CEE0700F7582041217C46F
:2003FD001214C77582041217FD90F0E374FDF09000281217A1121909AE82AF837877EEF227
:20041D00EF08F2E408F208F29003E8E4F5F0125F41121809D20022758201121884AC82AD60
:20043D0083AEF0FF7877ECF2ED08F2EE08F2EF08F29003E8E4F5F0125F41021809E582FF7B
:20045D0024FA50030204EEEF2F2F90046B7302495C02047D0204940204C40204EE0204D003
:20047D0074CBC0E07462C0E0125A4615811581C20090F959E4F02290F959E0FE74D8C0E0E6
:20049D007462C0E0125A4615811581758204C0061217C41213811214C77582041217FDD0B9
:2004BD000690F959EEF022123A19900000E4F5F002180974E8C0E07462C0E0125A4615819C
:2004DD0015817582041217C412276D7582040217FD7E00C007C00674F8C0E07462C0E0122B
:2004FD005A46E58124FCF581227582011217C475820F124C9E90FAB2E0FEA3E0FF7423C047
:20051D00E08E828F831248A615817404C0E01243EFAC82AD83158190F0E5ECF0EDA3F09085
:20053D0062BBE493C0E0740193C0E09062BDE493C0E0740193C0E0740293C0E0740AC0E08D
:20055D007463C0E0125A46E58124F9F58112531B1219A212195A90F0E374FCF0AEBE530602
:20057D00187F00BE1009BF000690F0E374FEF0301B0C123E94900000E4F5F01218097436F5
:20059D00C0E074FCC0E0E4C0E07439C0E07463C0E0125A46E58124FBF58112374E900190E1
:2005BD00E4F5F01218097582041217C41213F312373590F0C2E060117441C0E07463C0E045
:2005DD00125A4615811581801B7452C0E07463C0E0125A4615811581123AC8902710E4F516
:2005FD00F012180990F959E0FFE0601F8F82121C5EE5827025745FC0E07463C0E0125A4666
:20061D001581158190F959E4F0800F747DC0E07463C0E0125A461581158190F959E0700331
:20063D0012036D90F959E06007123CFAD2008005123DF8C2007582041217C41214C7300045
:20065D00067E287F0080047E587F028E828F831217A130000A7C887D137E007F0080087CBC
:1E067D00C07DD47E017F008C828D838EF0EF1218093000051201CC80F812036D80F311
:2062AF00C864C965CA66CB67CC68CD6910008F63804E6F207570646174650A00434D445F8A
:2062CF00444F5F5343414E0A0052657365742073657474696E67730A0045726173696E6727
:2062EF0020696D616765730A00436D6420307825782069676E6F7265640A000A2573204F53
:20630F0045504C2076253034782C20636F6D70696C656420417567202037203230323420A1
:20632F0030393A32353A33320A004D41432025730A00446F696E67206661737420626F6FE9
:20634F00740A004E6F726D616C20626F6F740A004E6F20415020666F756E64206F6E20732F
:20636F0061766564206368616E6E656C0A004E6F207361766564206368616E6E656C0A0078
:09638F004368726F6D6132390040
:017671000117
:20069B00E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB02
:2006BB00004380040022AC82AD83AEF0FF00C2A00075820312435C8E8212435C8D82124309
:2006DB005C8C8202435CC007C006C005C004C003C002C001C000AC82AD83AEF0FFE5812453
:2006FB00F5F8860208860300C2A00075820312435C8E8212435C8D8212435C8C8212435C8C
:20071B008A068B07E58124F3F886040886058C028D031CBCFF011DEA4B6015758200124301
:20073B005CAB828E828F83EBF0A3AE82AF8380DE00D2A000D000D001D002D003D004D00554
:20075B00D006D00722AF8200C2A0008F8212435C00D2A0002200C2A00075820512435C7542
:20077B00820012435CE58220E0F500D2A00022AC82AD83AEF0FF7803E2FA08E2FBC007C07D
:20079B0006C005C004C003C002120770758206120760D002D003D004D005D006D00700C26E
:2007BB00A00075820212435C8E8212435C8D8212435C8C8212435C8A068B077805E2FC080F
:2007DB00E2FD8C028D031CBCFF011DEA4B60128E828F83E0FBA3AE82AF838B8212435C8025
:2007FB00E100D2A000221207707582B9120760C20C227582AB12076075F034D5F0FDD20C73
:20081B0022C007C006C005C004C003C002C001C000C082C083C0F0C0E005810581E5812414
:20083B00EFF88605088604E58124EDF8E608467003020915C005C004E58124F9F8860208CF
:20085B007B00E58124FDF8E4C39AF674019B08F6E58124FDF8E58124EBF9C3E79609E70819
:20087B0096D004D005500EE58124EDF8A98119E6F708E609F77803EDF2EC08F2A881187949
:20089B0005E6F308E609F3E58124FBF886820886830886F008E6C007C006C005C004C0039A
:2008BB00C002C001C000C02512078AD025D000D001D002D003D004D005D006D007A8811880
:2008DB008602088603E4FEFFE58124FBF8EA26F6EB0836F6EE0836F6EF0836F6A88118E6F5
:2008FB002DFD08E63CFCE58124EDF8A98119E6C397F608E60997F6020842E58124FAF581DB
:20091B00D000D001D002D003D004D005D006D00722C007C006C005C004C003C002C001C0E2
:20093B0000C082C083C0F0C0E0E58124FDF88602088603EA7005EB540F6006758200020A19
:20095B002FE58124F1F8E608467003020A2C758206C007C006C005C004C003C002C001C0E2
:20097B0000C025120760D025D000D001D002D003D004D005D006D00700C2A000E58124F190
:20099B00F8860208860375822012435CE58124FDF80808868212435CE58124FDF80886828C
:2009BB0012435CE58124FDF8868212435C00D2A000C007C006C005C004C003C002C001C0A5
:2009DB0000C025120770D025D000D001D002D003D004D005D006D0077401C0E09010001236
:2009FB0043EFAC82AD83AEF0FF1581E58124FDF8EC26F6ED0836F6EE0836F6EF0836F61A12
:200A1B00BAFF011BE58124F1F8A60208A60302095C758201E58124FCF581D000D001D0024C
:0B0A3B00D003D004D005D006D0072265
:200A4600E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB53
:200A6600004380040022C007C006C005C004AF82AE83ADF0FC90F0BBEFF0EEA3F0EDA3F05B
:200A8600ECA3F0C202C203D208758204C007C006C005C004C003C002C001C000C025121749
:200AA600C4D025D000D001D002D003D004D005D006D00790F0BBE0FCA3E0FDA3E0FEA3E040
:200AC600FF7416C0E0E4C0E074F3C0E074F0C0E08C828D838EF0EF1206E1E58124FCF581D8
:200AE60090F0BBE0FCA3E0FDA3E0FEA3E0FF90F0BB74162CF0E43DA3F0E43EA3F0E43FA346
:200B0600F090F103E0FFBF2004C207806A90F103E0FFBF2104D207805E90F103E0FF7E0007
:200B2600C007C0067480C0E07469C0E0125A46E58124FCF58190FA6E7416F0E4A3F090F0FA
:200B4600F375F000C007C006C005C004C003C002C001C000C025121B11D025758204C02523
:200B66001217FDD025D000D001D002D003D004D005D006D0078051C221C007C006C005C0F2
:200B860004C003C002C001C000C025125C75D025C025120BD7D025D221C025125C75D0250A
:200BA600C025120BD7D025758204C0251217FDD025C025125CB9D025D000D001D002D0031F
:200BC600D004D005D006D007D004D005D006D007227F807E0090F0ABE4F0A3F090F0A5F01D
:200BE600A3F05390FD007D00200803020C6A30211D20071A74A0C0E07404C0E0E4C0E090CD
:200C0600F0F3124598158115811581020CBAC00590F0BBE0FAA3E0FBA3E0FCA3E0FD74A007
:200C2600C0E07404C0E074F3C0E074F0C0E08A828B838CF0ED1206E1E58124FCF58190F0F3
:200C4600BBE0FAA3E0FBA3E0FCA3E0FD90F0BB74A02AF074043BA3F0E43CA3F0E43DA3F066
:200C6600D005805074A0C0E07404C0E0E4C0E090F0F31245981581158115817B4A7C00C054
:200C860007C006C005C004C0037807E2F58208E2F583120081D003D004D005D006D00790AF
:200CA600F0A5E02401F0A3E03400F01BBBFF011CEB4C70CB7B007C00EF60503021238E02FF
:200CC600EA2AFEEB24F3F582EC34F0F583E0FAEF5A7003430601EF30E02B8E82125CAB8048
:200CE600248E02EA2A25E0FEEB24F3F582EC34F0F583E0FAEF5A6003430603EF5411600597
:200D06008E82125CABEFC313FF80AD7F800BBB00010CC3EB94A0EC9404409D90F0ABE0FB98
:200D2600A3E0FC744A2BFBE43CFC90F0ABEBF0ECA3F00DBD04005003020BEEE58620E0FB27
:200D4600004390020022AE82AF83C202C203C208D207C2217807EEF2EF08F2125C75120BDD
:200D6600D7D221125C75120BD7025CB9C007C006C005C004C003C002C001C000AD82AE8339
:200D8600AFF0A204202101B3505090F09FE0F508A3E0F50974012DFAE43EFB8F048A828B13
:200DA600838CF012628FFA780CF2E408F2850882850983C007C006C005C004C003C002C062
:200DC60001C000121003E582D000D001D002D003D004D005D006D0076003020FA98D828E6A
:200DE600838FF0780912628FF290F0A1E0F508A3E0F5097809E2F50A750B00780EE50AF2AD
:200E0600E50B08F2850882850983C007C006C005C004C003C002C001C00012107AD000D06A
:200E260001D002D003D00490F0ADE0F508A3E0F50990F0B1E0FBA3E0FCEBC39508F582EC6E
:200E46009509F5837875E50AF2E50B08F2C002C001C000125F21AB82AC83D000D001D0021A
:200E6600D005D006D007ECC423CBC423541F6BCB541FCB6BCBFC90F0B7EBF0ECA3F0740245
:200E86002BFBE43CFCEB2DFDEC3EFE90F0A7E0FBA3E0FCEB5407700A7809E2540770030259
:200EA6000F5D5303078BF005F07B80E4FC3392D28008A2D2EC13FCEB13FBD5F0F5780AEB6A
:200EC600F2780B7480F28D828E838FF012628FFA74012DF50CE43EF50D8F0E7809E2F50A4F
:200EE600AC0A150AEC7003020FA9780BE25A6025C00290F0B5E0FAA3E0FCEA24F3FAEC344F
:200F0600F0FC8A828C83E0FB780AE242038A828C83EBF0D002780BE2C313F2780BE270195D
:200F2600850C82850D83850EF012628FFAA385820C85830D780B7480F2780AE2C313F2782B
:200F46000AE2709C780A7480F290F0B5E02401F0A3E03400F080897809E2604790F0B5E032
:200F6600FBA3E0FCEB24F3FBEC34F0FC8B828C83E0F50C8D828E838FF012628FFAA3AD827D
:200F8600AE83E50C42028B828C83EAF090F0B5E02401F0A3E03400F07809E2FC780924F822
:200FA600F280B4D000D001D002D003D004D005D006D0072290F0A7E0FEA3E0C423CEC42323
:200FC600541F6ECE541FCE6ECE30E40244E0FF90F0ABE0FCA3E0FD90F0B1E0FAA3E0FBEAAC
:200FE600C39CFCEB9DC454F0CCC4CC6CCC54F0CC6CFD90F0B5EC2EF0ED3FA3F022AE82AFF4
:201006008390F095EEF0EFA3F090F0AFEEF0EFA3F07427C39EFE74019FFF90F0A9EEF0EF40
:20102600A3F090F0A5E0FCA3E0FD90F0B1ECF0EDA3F0C3EE9CEF64808DF063F08095F040D4
:201046002F780CE22CFC08E23DFDC3EE9CEF9D501F780CD3E29EF4B3FEB308E29FF4FF0EA8
:20106600BE00010F90F0ADEEF0EFA3F07582002275820122AE82AF8390F0A7EEF0EFA3F0F3
:20108600780E90F097E22EF008E23FA3F0120FBA75820022C007C006C005C004C003C00262
:2010A600C001C000AC82AD83AEF0FF058105810581BF2000502C8F82C007C006C005C0049A
:2010C600C003C002C001C000C0251242A7D025D000D001D002D003D004D005D006D0070291
:2010E60013447E00EF24E0FFEE34FFFEEF2FFFEE33FEEF2498F582EE3463F583E493FEA391
:20110600E493FF90F0B7EEF0EFA3F0EFC4540FFE90F0B3F0300507EE2EFE90F0B3F090F08C
:2011260099E0FEA3E0FF90F0B9EEF0EFA3F090F0B3E0FDFB3395E0FC0BBB00010CEB2EFB81
:20114600EC3FFC90F099EBF0ECA3F0A204202101B3504190F0B9E0FEA3E0FF780CEDF2EDDA
:201166003395E008F28E828F83C007C006C005C004C003C002C001C000C025121003E58218
:20118600D025D000D001D002D003D004D005D006D007600302134490F09BE0FEA3E0FF90F1
:2011A600F0B4E0FD780EF2ED3395E008F28E828F83C007C006C005C004C003C002C001C063
:2011C60000C02512107AD025D000D001D002D003D004D005D006D00790F0B7E0FEA3E0FF60
:2011E60053070F90F0B7EEF0EFA3F0A8811818760008768090F0A7E0FCA3E05304078CF0BC
:2012060005F07C80E4FD3392D28008A2D2ED13FDEC13FCD5F0F5A881A60490F0B3E0FD903E
:20122600F0ADE0FAA3E0FB90F0A5E0FCA3E0FFECC39AFAEF9BFBED3395E0FFEDC39AFAEFA1
:201246009BFB1ABAFF011B90F0B9EAF0EBA3F030050FEBC313CA13CAFB90F0B9EAF0EBA32A
:20126600F090F0B9E0FEA3E0FF90F0B7E0FCA3E0FD90F0B7EE2CF0EF3DA3F090F0B7E0FE37
:20128600A3E0FFEE2EFEEF33FFEE2458F582EF3464F583E493FEA3E493FF90F0B9E4F0A36D
:2012A600F090F0B9E0FCA3E0FD90F0B4E0FB3395E0FAC3EC9BED9A4003021344A88118182C
:2012C600E65EFC08E65FFD4C602190F0B5E0FCA3E0FDEC24F3FCED34F0FD8C828D83E0FB1A
:2012E600A881E642038C828D83EBF030051A90F0B9E0FCA3E0FDEC30E01AA88118E618C39F
:2013060013C613C608F6800CA88118E618C313C613C608F6A881E6C313F6A881E6701090DF
:20132600F0B5E02401F0A3E03400F0A881768090F0B9E02401F0A3E03400F00212A7158121
:1513460015811581D000D001D002D003D004D005D006D00722A8
:206398000070071008500D9016601CA026A03010316037603D7044904D204F605520578020
:2063B8005F7066706D6073507880805085708C7093709A70A120A320A570AC80B470BB70B6
:2063D800C2A0CCA0D670DD80E570EC70F370FA8002710971105115811D7124812C7133813E
:2063F8003B7142814A7151715891617168A172A17CA186A1908198519D81A551AA81B2A1F2
:20641800BC21BE71C571CC71D371DA71E181E971F071F741FB51007207420B9214721B724A
:2064380022722972306236623C724362499252A25C8264A26E8276627C127D6283A28D728C
:206458000000000000000000000000000000D87F0078000000000000007880009804E005DC
:20647800801E9864E005801E80648004183808640842FCFF8841F040083810442044C04489
:2064980000397002880C881088207040E0001001083A08468845C84C3838180068008001E2
:2064B8000078E00718180C300420024002400240024004200C301818E00700100018000F1F
:2064D8000072000F001800104000400040004000F807400040004000400019001E00800045
:2064F800800080008000800080001800180002000C003000C0000003000C00300040C00F88
:2065180030300840084008403030C00F082008200820F87F0800080008001860284048408D
:2065380088400843083C0840084208420842F03DC0004003400440184020F87F40004000A3
:20655800087C084408441042E041E00F10320844084408441042E00100401840E040004351
:20657800004C00500060F038084508420842084590456038001E0821884088408840302154
:20659800C01F1806180619061E06C000C0002001200110021002080420012001200120010F
:2065B800200120012001200108041002100220012001C000C00000700040D8400041004202
:2065D80000640038C00F30181820C84728486850D851E03F2000200008007000C001400E72
:2065F8004018400C4003C00030000800F81F08110811081108118812700CC003300C1008FC
:2066180008100810081008100818F81F08100810081008101008E007F81F8810881088108D
:20663800881088100810F81F801080108010801080100010C003300C100808100810881024
:206658008810F818F81F80008000800080008000F81F081008100810F81F08100810081025
:206678000800081008100810F01FF81F0001800140022004200810100800F81F080008002D
:206698000800080008000800F81F001C8007E00060008003001CF81FF81F0008000680016C
:2066B80060001000F81FE007100808100810081008101008E007F81F801080108010801086
:2066D8000011000EE00710080810081008100C101208E207F81F80108010C0102011100EC7
:2066F8000800180E0812081108118810901070180010001000100010F81F001000100010C1
:206718000010E01F18000800080008001000E01F0010000C8003600018001800E000000301
:20673800000C00100018C0073800F0000007800370003800C0070018081010082004400277
:206758008001800140022004100808100010000800060001F8000001000200040008001053
:2067780018102810481088100811081208140818FE7F024002400240024000400030000C41
:206798000003C00030000C0002000240024002400240FE7F2000C0000007001C0070000EDA
:2067B800C0012000040004000400040004000400040004000400040000800040300048047C
:2067D800880488049004F8030800F87F10020804080408041006E001E00110020804080443
:2067F800080408040804E00118020804080408041002F87FE0019002880488048804880412
:20681800880300040004F83F00240044004400440044E00119020904090409041202FC0728
:20683800F87F00010002000400040004F803000400040064F8670100010401040164FE671F
:20685800F87F8000C0002001200210020804004000400040F87FF807000200040004F803CD
:20687800000200040004F803F80700030002000400040004F803E0011002080408040804D9
:206898001002E001FF0710020804080408041006E001E00118020804080408041002FF0783
:2068B800F8070001000200040004000718038804880448044804300400040004F01F08048D
:2068D800080408040804F0070800080008001000F807000480036000180008003000C00067
:2068F800000300040006E001180070008003800170001800E0010006080410022001C00098
:20691800C00020011002080401040103C10062001C001800600080000003000408041804F1
:20693800280448048804080508060804800080007C3F024002400240FE7F02400240024050
:206958007C3F80008000C000000100010001800080004000400040008001F8010803080450
:20697800080808040803F80164617461547970652030782578206E6F7420737570706F7204
:056998007465640A00B3
:20135B00E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB35
:20137B0000438004002290F0C17401F090F0C2E4F090F0C3F090F0C4F090F0C5F090F0C6CB
:20139B0004F090F0C7F090F0C9E4F090F0C8F090F0CC7428F0E4A3F090F0CEF090F0CA7467
:2013BB0028F0740AA3F090F959E4F090F95AF022AD82AE8312628FFCBC011C7BC17CF08BD3
:2013DB00828C83740EC0E0C005C0061246301581158115810214C7227480C0E0E4C0E074DF
:2013FB00A2C0E074FDC0E0906000E4F5F01206E1E58124FCF581740EC0E07414C0E074FE15
:20141B00C0E090F0C112463015811581158190FDA2E0FCA3E0FDA3E0FEA3E0FFBCA513BDC7
:20143B005A10BEBA0DBFAB0A90FDA6E0FFBF0102803590FDA2E0FCA3E0FDA3E0FEA3E0FF17
:20145B0090FDA6E0FB7A00C004C005C006C007C003C002749DC0E07469C0E0125A46E58108
:20147B0024F8F58102138190FDA7E090F959F090FDA8E090F95AF074CBC0E07469C0E012ED
:20149B005A461581158112161990FDA2E4F004C0E0E4C0E074A2C0E074FDC0E0906000E45E
:2014BB00F5F012081CE58124FCF581227480C0E0E4C0E074A2C0E074FDC0E0906000E4F530
:2014DB00F01206E1E58124FCF58190FDA2E0FCA3E0FDA3E0FEA3E0FFBCA53EBD5A3BBEBA15
:2014FB0038BFAB35788574C1F274F008F2E408F27888740EF2E408F290FE1475F0001261CE
:20151B0010E5828583F045F0700F90FDA7E0FF90F959E0FEEFB506012290FE21E0FF90F0DF
:20153B00CEE0FEEFB50602803EEE603BBE6400502390FE21E0FF7D00C007C00574DDC0E0D4
:20155B007469C0E0125A46E58124FCF58190F0CEE4F08013BEC800400890F95A7402F080F9
:20157B000690F95A7401F07480C0E0E4C0E0C0E090FDA2124598158115811581740EC0E048
:20159B0074C1C0E074F0C0E090FE1412463015811581158190FDA274A5F0F4A3F074BAA3DB
:2015BB00F0C4A3F090FDA67401F090F959E090FDA7F090F95AE090FDA8F07401C0E0E4C0AA
:2015DB00E0906000E4F5F012092C158115817480C0E0E4C0E074A2C0E074FDC0E090600015
:2015FB00E4F5F012081CE58124FCF58174F6C0E07469C0E0125A461581158102161990F0BF
:20161B00C5E0FF7E00C007C0067407C0E0746AC0E0125A46E58124FCF58190F0CEE0FF7E0E
:20163B0000C007C0067420C0E0746AC0E0125A46E58124FCF58190F0CAE0FEA3E0FFC00632
:20165B00C0077433C0E0746AC0E0125A46E58124FCF58190F0C2E0FF7E00C007C00674474E
:20167B00C0E0746AC0E0125A46E58124FCF58190F959E0FF7E00C007C006745CC0E0746A69
:0C169B00C0E0125A46E58124FCF58122D3
:20699D004C6F616465642064656661756C74732073657474696E67735665722030782578F6
:2069BD00204D61676963203078256C780A004C6F616465642073657474696E67733A0A00C0
:2069DD0049676E6F7265642066697865644368616E6E656C2025640A0053617665642073B0
:2069FD00657474696E67733A0A0020205363616E2041667465722054696D656F7574202515
:206A1D00640A00202066697865644368616E6E656C2025640A0020206261744C6F77566FC1
:206A3D006C746167652025640A002020656E61626C6546617374426F6F742025640A0020DD
:0D6A5D00204C6173742063682025640A00DA
:2016A700E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FBE6
:2016C70000438004002275F10175805875FDDD75F33875904075FE6E75F4E875A00175FF76
:2016E70001227401C0E0E4C0E0900000E4F5F012092C158115817414C0E0E4C0E0746AC011
:20170700E0746AC0E090F0F31245F6E58124FCF5817414C0E0E4C0E074F3C0E074F0C0E0F1
:20172700900000E4F5F012081CE58124FCF58122C00775E40075FC0075F70075FE0290F9FA
:2017470053E0FF54F06002800DD291758F0075F60075FD008012758F8175F62F75FD8175C0
:14176700FE2F53807E5390D075F30075F40075F500D0072209
:146A6A005612098501084A41000000000904EC0212045A0A19
:027FFE00706AA7
:20177B00E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB11
:20179B00004380040022AE82AF837D00BD08005017ED75F002A424CFF58274F035F0F583D7
:2017BB00EEF0EFA3F00D80E422E582FF30E025125657C0071249C9D2AF1216CD12435812A1
:2017DB004E6FD00775820A124EAE90F959E0FEF582124DFAEF30E209124385200C0302089E
:2017FB000D22E58230E206300C0302080122AF82AE83ADF0FC90F0EFEFF0EEA3F0EDA3F06A
:20181B00ECA3F090F0EFE0FCA3E0FDA3E0FEA3E0FFC004C005C006C007747EC0E0746AC01A
:20183B00E0125A46E58124FAF581300C16758204C007C006C005C0041217FDD004D005D0FF
:20185B0006D007C007C006C005C004125C62D004D005D006D0071217378C828D838EF0EF69
:20187B00124A517582010217C4E582FF601090F0E4E0FFBFFF00500690F0E4EF04F090F0D7
:20189B00E4E0FFBF18005007900E10E4F5F022BF24005007901C20E4F5F02290518075F0EC
:2018BB0001E42290F0E0E07875F2E408F2900230125F217862740EF2E408F2125D4AAE82A0
:2018DB00AF8374282EFEE43FFF90F0DFE054077D0025E0FCED33FDEC24CFF582ED34F0F540
:2018FB0083EEF0EFA3F090F0DFE02401F0227E007F007D00BD0800501DED75F002A424CFDD
:20191B00F58274F035F0F583E0FBA3E0FCEB2EFEEC3FFF0D80DEEFC423CEC423541F6ECEF4
:20193B00541FCE6ECEFF90F0CCE0FCA3E0FDC3EE9CEF9D50058C828D83228E828F83229026
:20195B00FAB4E0FF3395E0FEC007C0067493C0E0746AC0E0125A46E58124FCF58190F0BF9A
:20197B00E0FF7E0090F0C0E0FD3395E0FCC007C006C005C004749EC0E0746AC0E0125A4636
:20199B00E58124FAF5812275820E124C9E124CCA75820F124C9E90FAB2E0FEA3E0FF7423B2
:2019BB00C0E08E828F831248A615817404C0E01243EFAC82AD83158190F0EBECF0EDA3F09D
:2019DB00C20A90F0CAE0FEA3E0FFC3EC9EED9F5004D20A803A90F0E7E0FCA3E0FD4C601331
:2019FB00C3EC9EED9F500C90F0EBECF0EDA3F0D20A801C90F0E9E0FCA3E0FD4C6011C3EC27
:201A1B009EED9F500A90F0EBECF0EDA3F0D20A90F0E9E0C0E0A3E0C0E090F0E7E0C0E0A3EE
:201A3B00E0C0E090F0EBE0C0E0A3E0C0E090F0E5E0C0E0A3E0C0E074AFC0E0746AC0E012A2
:091A5B005A46E58124F6F58122CA
:206A7E00536C656570696E6720666F7220256C64206D730A0054656D7020256420430A008F
:206A9E00525353492025642C204C51492025640A00426174745620426F6F742025642C207F
:1A6ABE006E6F772025642C2054782025642C207570646174652025640A007E
:0500DC00781074FFF232
:201A6400E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB25
:201A8400004380040022121AE0E582600790F96CE0F58222121AAAE582600790F970E0F59F
:201AA40082227582002290F95BE05407FFBF012890F95CE003035403FFBF031C90F95CE09B
:201AC40023235403FFBF031090F95BE023235401FFBF0104758201227582002290F95BE07B
:201AE4005407FFBF012390F95CE003035403FFBF021790F95CE023235403FFBF030B90F9F5
:201B04005BE020E6047582012275820022AFF0AE83E58290FA70F0EEA3F0EFA3F090FA702B
:201B2400E0FDA3E0FEA3E0FF90FA6EE0FBA3E0FC79007A00C3E99BEA9C5052E94A6016E97B
:201B4400540F6002800F74D8C0E0746AC0E0125A4615811581C003C004E92DF8EA3EFB8F9E
:201B64000488828B838CF012628FF87C00C000C00474DAC0E0746AC0E0125A46E58124FC2A
:201B8400F58109B900010AD004D00380A774D8C0E0746AC0E0125A461581158122AFF0AE79
:201BA40083E5827812F2EE08F2EF08F27815E4F27B017811C3E2F5F0EB95F050227812E2AA
:201BC400FA08E2FC08E2FFEB2AFAE43CFC8A828C838FF012628FFA7815E22AF20B80D37815
:201BE40012E2FD08E2FE08E2FF8D828E838FF012628FFD7815E2B50503D38001C3E433F531
:201C04008222AFF0AE83E5827817F2EE08F2EF08F2781AE4F27B017816C3E2F5F0EB95F027
:201C240050227817E2FA08E2FC08E2FFEB2AFAE43CFC8A828C838FF012628FFA781AE22A94
:201C4400F20B80D37817E2FD08E2FE08E2FF8D828E838FF0781AE2025F06E5812404F58173
:201C6400AF82C218C007125098D0078F82124DFAC00712514DD218125098D0077E01EE2490
:201C8400FC5003021D8890F9DB7414F0C007C00612379590F9ED74EDF090F9DB124F94D013
:201CA40006D007124981AA82AB83ACF0FD90FA7374A62AF0740E3BA3F0E43CA3F0E43DA37C
:201CC400F0C006124981AA82AB83ACF0FD90FA73E58124FCF8E0F6A3E008F6A3E008F6A385
:201CE400E008F6E58124FCF8C3EA96EB0896EC0896ED0896D0064003021D84C007C0061248
:201D040050AAAD82D006D007C374818DF063F08095F050AD90F971E0FDB507A5C007C0069A
:201D2400121A8AAD82D006D007BDEE95C007C006121AAAE582D006D00760867408C0E074E0
:201D440068C0E074F9C0E090F94612463015811581158190F95EE0FCA3E0FD90F94EECF05B
:201D6400EDA3F08F047D00C004C00574E0C0E0746AC0E0125A46E58124FCF5818F82802075
:201D84000E021C828F057E00C005C00674EAC0E0746AC0E0125A46E58124FCF581758200D3
:201DA400E58124FCF5812290F9DB742AF012379590F9ED74E5F090F9F47482F090F0E3E0CC
:201DC40090F9F5F090F0C0E090F9F0F090F0BFE090F9EFF090FAB4E090F9F1F090F0EBE0AE
:201DE400FEA3E0FF90F9F2EEF0EFA3F090F9F6E4F09062BB93FE740193FF90F9F7EEF0EF9F
:201E0400A3F090F959E090F9F9F090F0C9E090F9FAF078167415F290F9EE75F000121C0642
:201E240090F9DB024F94D2181250981219A212195A7F00781BE4F2781BE2B40E00400302BB
:201E44001F08C007121DABD007124981AA82AB83ACF0FD74882AF52674133BF527E43CF5DC
:201E640028E43DF529C007124981AC82AD83AEF0FFC3EC9526ED9527EE9528EF9529D00717
:201E84004003021EFBC0071250AAAE82D007C374818EF063F08095F050CBC007121A8AAE32
:201EA40082D007BEE6BF78117411F290F97175F000C007121BA1E582D00760A97408C0E00B
:201EC4007468C0E074F9C0E090F94612463015811581158190F95E75F00012628FFDA3125B
:201EE400628FFE90F94EEDF0EEA3F090F0E0EFF090F97175F00022781BE22401F2781BE2F9
:201F0400FF021E3B90F0E0740EF090000075F00022D2181250987F00781CE4F2781CE2B483
:201F24000E004003021FFC90F9DB7414F090F9ED74E3F0C00712379590F9DB124F94D007C1
:201F4400124981AA82AB83ACF0FD74882AF52A74133BF52BE43CF52CE43DF52DC0071249E1
:201F640081AC82AD83AEF0FFC3EC952AED952BEE952CEF952DD0075072C0071250AAAE82CA
:201F8400D007C374818EF063F08095F050CEC007121A8AAE82D007BEE6C278117411F29040
:201FA400F97175F000C007121BA1E582D00760AC7408C0E07468C0E074F9C0E090F94612E9
:201FC400463015811581158190F95E75F00012628FFDA312628FFE90F94EEDF0EEA3F09010
:201FE400F0E0EFF090F97175F00022781CE22401F2781CE2FF021F2090F0E0740EF0900008
:202004000075F00022AFF0AE83E582781DF2EE08F2EF08F2781DE22402FA08E23400FB08EE
:20202400E2FC8A828B838CF012628FF52E852E2F75300090FA5CE0F531AE317F00E52FC35A
:202044009EFEE5309FFF7875EEF2EF08F2900063C004C003C002125F21AE82AF83D002D0A5
:2020640003D0048E2F8F3090FA77E52FF0E530A3F0781DE22401FD08E23400FE08E2FF8D31
:20208400828E838FF012628FFD90F91FE0FFEDB50702800122E52E24D5500122C3E52E956B
:2020A400315001227463252FFEE43530FFC374239E74089F500122781DE2FD08E2FE08E23B
:2020C400FF78117466F28D828E838FF0C004C003C002121BA1E582D002D003D0047003029D
:2020E400217D781DE22403FD08E23400FE08E28D2F8E3090FA77E0FEA3E0FFEE24F3FEEFD0
:2021040034F0FF8E828F837463C0E0E4C0E0C02FC0301245F6E58124FCF5818A828B838CAD
:20212400F012628FFA7F00788A7408F2E408F28A828F83C007C0021261CAAD82AE83D002CB
:20214400D007ED2421FDEE34F9FE5302078AF005F07401800225E0D5F0FBF4FA8D828E83C7
:20216400E052028D828E83EAF074F7C0E0746AC0E0125A46158115812274F9C0E0746AC0F9
:20218400E0125A461581158122AF82AE83ADF0FC7820EFF2EE08F2ED08F2EC08F290FA5C4C
:2021A400E0FB7A0090F91FE0FF7E00C003C002C007C006740AC0E0746BC0E0125A46E581FA
:2021C40024FAF581D21812509812498185823285833385F034F53578207977E22414F308C3
:2021E400E2340009F308E2340009F308E2340009F3900019E4F5F0125F41AA82AB83AEF079
:20220400FFEA2532F532EB3533F533EE3534F534EF3535F535124981AA82AB83AEF0FFC33F
:20222400EA9532EB9533EE9534EF9535503C1250AAAF82C374818FF063F08095F050D61236
:202244001A8AAF82BFE8CE90F97175F0001220097F00EF2421FDE434F9FE8D828E83E07067
:20226400060FBF060040EBBF06AB74D8C0E0746AC0E0125A4615811581C218125098025176
:202284004D90F972E4F0A3F090F97175F00022742AC0E0E4C0E0C0E090F9DB1245981581BF
:2022A4001581158190F9DB742AF090F940E0600890F9F174E4F0800690F9F174E7F0740862
:2022C400C0E07450C0E074F9C0E090F9E91246301581158115817408C0E07446C0E074F94A
:2022E400C0E090F9E112463015811581158190F9DCE054F84401F090F9DCE054C74440F0EC
:2023040090F9DDE0440C54CF44C0F090F958E0FF04F090F9DEEFF090F94EE0FEA3E0FF904B
:20232400F9DFEEF0EFA3F07DF27EF98D828E837411C0E07416C0E074F9C0E01246301581E1
:20234400158115817DF27EF97F0078167411F28D828E838FF0121C0690F9DB024F94E58161
:202364002404F581D21812509812514D7F00BF1E004003022445C007122293D007124981DC
:20238400AB82AC83ADF0FE90FA7974642BF0749F3CA3F0740B3DA3F0E43EA3F0C00712503D
:2023A400AAAE82D007C374818EF063F08095F05051C007121A8AAE82D007BEE8028028BEA7
:2023C400E9028005BEEC33802978117403F290F97175F000C007121BA1E582D007602390CC
:2023E400F97175F0008063122285AC82AD83AEF0805890000075F000805090000075F000E0
:202404008048C007124981C8E58124FCC8A68208A68308A6F008F690FA79E0FAA3E0FDA342
:20242400E0FEA3E0FFE58124FCF8C3E69A08E69D08E69E08E69FD00750030223A00F0223B0
:2024440072122285AD82AE83AFF0E58124FCF58122741BC0E0E4C0E0C0E090F9DB1245988A
:2024640015811581158190F9DB7419F090F9F174EAF07408C0E07450C0E074F9C0E090F9D7
:20248400E91246301581158115817408C0E07446C0E074F9C0E090F9E11246301581158164
:2024A400158190F9DCE054F84401F090F9DCE054C74440F090F9DDE0440C54CF44C0F090AB
:2024C400F94EE0FEA3E0FF90F9DFEEF0EFA3F090F958E0FF04F090F9DEEFF090F9DB024FDA
:2024E40094D2181250987F00BF1000506CC007122455D00712498185823A85833B85F03C1C
:20250400F53D12498185823685833785F038F539E536C3953AFAE537953BFCE538953CFDD8
:20252400E539953DFEC3EA94A6EC940EED9400EE94005022C0071250AAAE82D007C374812D
:202544008EF063F08095F050B9C007121A8AAE82D007BEEBAD220F808F22AF82E4FDCD5429
:2025640003A2E0CD13CD13A2E0CD13CD13CDFCEF0303543F4DFDEF030354C0FE7F0074013A
:202584002DFDE43C8F828E838DF022AD82AE83AFF07C007B00BB0400400302261EC0048B9F
:2025A40082C007C006C005C00312255EA882A983AAF0FCD003D005D006D0077416C0E0E49C
:2025C400C0E074F3C0E074F0C0E0888289838AF0EC1206E1E58124FCF5817464C0E074FCF3
:2025E400C0E090F0FB12490315811581920ED004300E218D008E018F027408C0E0C000C016
:202604000190F0F31245C4158115811581920E50038C82220B8B040225997582FF22AF829F
:202624005307F87E007D00BD040040030226AAC0068D82C007C00512255EAA82AB83ACF087
:20264400FED005D0077416C0E0E4C0E074F3C0E074F0C0E08A828B838CF0EE1206E1E58130
:2026640024FCF5817464C0E074FCC0E090F0FB124903158115819228D006302823C00690D2
:20268400F108E0FC5304F87B008F027E00ECB50206EBB506028004D0068005D0068E822250
:2026A4000D8D0602262B7582FF2212255EAC82AD83AEF0FF7416C0E0E4C0E074F3C0E07452
:2026C400F0C0E08C828D838EF0EF1206E1E58124FCF58190F108E0F58222AF828F060EEE22
:2026E40024FB50027E00EEB507038E82228E82C007C00612255EAA82AB83ACF0FDD006D03D
:20270400077416C0E0E4C0E074F3C0E074F0C0E08A828B838CF0ED1206E1E58124FCF5817D
:202724007464C0E074FCC0E090F0FB12490315811581922850A890F108E0FD5305F87C0024
:20274400BD789BBC00988E822212255EAC82AD83AEF0FF7404C0E0E4C0E08C828D838EF057
:20276400EF12092C15811581227F00BF0400500C8F82C00712274DD0070F80EF22AF828F9E
:20278400057E00C005C0067419C0E0746BC0E0125A46E58124FCF5818F8212255E020A6CAF
:2027A4007825E4F208F208F208F27B00BB040040030228438B82C00312255E85823E858318
:2027C4003F85F040F541D0037416C0E0E4C0E074F3C0E074F0C0E0853E82853F838540F0FE
:2027E400E5411206E1E58124FCF5817464C0E074FCC0E090F0FB12490315811581920F503C
:202804003AC00390F104E0FAA3E0FBA3E0FEA3E0FF7825C3E29A08E29B08E29E08E29FD095
:2028240003501890F1047825E0F2A3E008F2A3E008F2A3E008F290F945EBF00B0227B078B9
:2028440025E2F58208E2F58308E2F5F008E222AF82AE83D21090F91FE0FD7C007B007A007F
:202864008C44EBC454F0C544C4C5446544C54454F0C5446544F545EDC4540F4544F544ED16
:20288400C454F0F543754200300D17782FE542F27470254308F2E4354408F2E4354508F234
:2028A40080497810E2F582C007C00612255E85824685834785F048F549D006D00774162555
:2028C40046F546E43547F547E43548F548E43549F549782FE5462542F2E547354308F2E5EB
:2028E40048354408F2E549354508F290FA807405F090FA5CE4F090F94004F07829EFF20803
:20290400EEF27406C0E0E4C0E0C0E090F92112459815811581158190FA80E0FB14F0EB70F6
:2029240003022C961010030229E57829E2FA08E2FB8A828B8378627463F2E408F2125D4AE3
:20294400AA8290FA7DEAF07829E2FB08E2FF8B828F8378837463F2E408F2C00212608DE598
:20296400828583F0D00245F0600690FA7DEA04F090FA7DE024EA500690FA7D7415F090FA32
:202984005CE0FB7A0090FA7DE0FFC3EA9F50528B067F00788A7408F2E408F28E828F83C06E
:2029A40007C006C003C0021261CAAC82AD83D002D003D006D007EC2421FCED34F9FD530637
:2029C400078EF005F07401800225E0D5F0FBFE8C828D83E0FF42068C828D83EEF00B0A80E9
:2029E400A4122362AA82AB83AFF0EA4B7003F582220ABA00010B8A828B838FF012628FFEF4
:202A0400A312628FFF4E60428E048F05C3EC9423ED9400501A7877EEF2EF08F2E408F20808
:202A2400F2900019E4F5F0125F411249E2801BEC24F6FCED34FFFD7F007E008C828D838FDB
:202A4400F0EE121809D218125098900122E4F5F012218D78337401F290FA5CE0FF7E00905C
:202A6400FA7DE0FDC3EE9D505E8F047D00788A7408F2E408F28C828D83C007C006C005C074
:202A8400041261CAAA82AB83D004D005D006D007EA2421F582EB34F9F5835304078CF0052C
:202AA400F07C017D008006EC2CFCED33FDD5F0F7E0FB7A005204EA5205EC4D60067833E49B
:202AC400F280040F0E80987833E27003022C5B90FA7DE07875F2E408F2900063125F21AEE7
:202AE40082AF83782DEEF2EF08F290FA8174F3F074F0A3F0E4A3F090FA5CE0704B90F0F3EC
:202B0400E0FCA3E0FD7829ECF208EDF290F0F5E0FCA3E0FD782BECF208EDF290FA7EE4F0DA
:202B2400A3F0782DE2FC08E2FDEC24FCFCED34FFFD782DECF2ED08F290FA81740424F3F07B
:202B4400E434F0A3F0E4A3F07829E2FC08E2FD782DC3E2F5F0EC95F008E2F5F0ED95F050C8
:202B64000A7829792DE2F308E209F37829E2FC08E2FD782DD3E29CF4B3FCB308E29DF4FD1B
:202B84007829ECF208EDF27582041217C490FA81E0F546A3E0F547A3E0F548AA46AF4778E0
:202BA4002DE2C0E008E2C0E0C002C007782FE2F58208E2F58308E2F5F008E212081CE58198
:202BC40024FCF5817582041217FD782DE2FA08E2FD7E007F00782FE22AF208E23DF208E22D
:202BE4003EF208E23FF2AD46AE47AF48782DE2F54608E2F547AA46AC47154674FFB5460211
:202C04001547EA4C60368D828E838FF012628FFCA3AD82AE838C4275430090FA7EE0FAA3DC
:202C2400E0FC8A4A8C4BAB42AC43EB254AFBEC354BFC90FA7EEBF0ECA3F080B990FA7DE0EE
:202C4400FF90FA5CE02FF07829E2FE08E2FF4E60067833E4F2D21090F940E4F07833E27071
:202C64000302291B782BE2FE08E2FF90FA7EE0FCA3E0FDEEB50406EFB5050280117433C0E7
:202C8400E0746BC0E0125A461581158180047582012275820022AFF0AE83E58290FA88F003
:202CA400EEA3F0EFA3F090FA88E0FDA3E0FEA3E00DBD00010E8D828E837408C0E07430C0A1
:202CC400E074F9C0E01245C41581158115819211501490F938E0FCA3E0FDA3E0FEA3E0FFFF
:202CE400EC4D4E4F707490F91FE4F090FA88E0FDA3E0FEA3E0FF74012DFAE43EFBC002C06D
:202D04000390F9171244C615811581740D2DFAE43EFB8F048A828B838CF012628FFA90F950
:202D240020F07411C0E0C005C00690F92F1246301581158115817408C0E0E4C0E09070002D
:202D4400E4F5F012092C1581158190F938E0FEA3E0FF90FA86EEF0EFA3F090F938E0FCA362
:202D6400E0FDA3E0FEA3E0FFEC4D4E4F7003022DF3C3E49C74109DE49EE49F500B90FA8432
:202D8400E4F07410A3F0800890FA84ECF0EDA3F0D20D90FA84E0FEA3E0FF8E828F831228A9
:202DA40053E582604690F91FE0FF0F90F91FEFF090F938E0FCA3E0FDA3E0FEA3E0FF90FAE8
:202DC40084E0FAA3E0FB8A008B01E4FAFBECC398FCED99FDEE9AFEEF9BFF90F938ECF0EDC5
:202DE400A3F0EEA3F0EFA3F0022D5E7582002275820122AFF0AE83E58290FA8DF0EEA3F0BA
:202E0400EFA3F090FA8DE0FDA3E0FEA3E0FF74012DFAE43EFB8F048A828B837408C0E0743F
:202E240030C0E074F9C0E01245C41581158115819212504090F93DE0FC740E2DFDE43EFE32
:202E44008D828E838FF012628FFDECB5052690F938E0FCA3E0FDA3E0FEA3E0FFEC4D4E4F0D
:202E64006012744BC0E0746BC0E0125A46158115810230017582041217C490F9457834E0AB
:202E8400F290FA8DE0FCA3E0FDA3E0FE740E2CFAE43DFB8E078A828B838FF012628FFA5405
:202EA400FC60317834E2FB7F00C003C007745EC0E0746BC0E0125A46E58124FCF58190FAC6
:202EC4006E7411F0E4A3F08C828D838EF0121B117834E4F290F945E02401F0FFBF04004073
:202EE4000590F945E4F090F945E0FF7834E2B507197582041217FD7482C0E0746BC0E012CF
:202F04005A4615811581758201228F8212255EAC82AD83AEF0FF7416C0E0E4C0E074F3C051
:202F2400E074F0C0E08C828D838EF0EF1206E1E58124FCF5817464C0E074FCC0E090F0FB26
:202F4400124903158115819212500890F108E054F8708190F945E0FF7810F28F8212255E74
:202F6400AC82AD83AEF0FF7404C0E0E4C0E08C828D838EF0EF12092C1581158175820412AB
:202F840017FD7810E2FE7F00C006C007748CC0E0746BC0E0125A46E58124FCF58190F91F30
:202FA400E4F090FA8DE0FDA3E0FEA3E0FF74012DFAE43EFBC002C00390F9171244C61581B2
:202FC4001581740D2DFAE43EFB8F048A828B838CF012628FFA90F920F07411C0E0C005C029
:202FE4000690F92F12463015811581158190F938E0FEA3E0FF90FA8BEEF0EFA3F090F9386E
:20300400E0FCA3E0FDA3E0FEA3E0FFEC4D4E4F7003023098C3E49C74109DE49EE49F500B76
:2030240090FA84E4F07410A3F0800890FA84ECF0EDA3F090FA84E0FEA3E0FF8E828F8312FF
:203044002853E582604A90F91FE0FF0F90F91FEFF090F938E0F54CA3E0F54DA3E0F54EA3B3
:20306400E0F54F90FA84E0FAA3E0FBE4FEFFE54CC39AFAE54D9BFBE54E9EFEE54F9FFF9060
:20308400F938EAF0EBA3F0EEA3F0EFA3F0023001758200227582041217C47430C0E074F9BB
:2030A400C0E090F0F31244C61581158190F0FB7421F07447A3F0744DA3F07447A3F090F938
:2030C40041E02401F0A3E03400F0A3E03400F0A3E03400F090F941E0FCA3E0FDA3E0FEA377
:2030E400E0FF90F104ECF0EDA3F0EEA3F0EFA3F090FA8BE0FEA3E0FF7D007C0090F0FFEE2E
:20310400F0EFA3F0EDA3F0ECA3F090F93CE090F103F090F93DE090F108F07810E2F582120F
:20312400255EAC82AD83AEF0FF7416C0E0E4C0E074F3C0E074F0C0E08C828D838EF0EF12B7
:20314400081CE58124FCF5817582041217FD75820122AFF0AE83E58290FA91F0EEA3F0EF5E
:20316400A3F0C20D90FA91E0F553A3E0F554A3E0F555740D2553FAE43554FBAC558A828B1A
:20318400838CF012628FFCBC02028024BC03030234DEBC10028019BC20028014BC210280BB
:2031A4000FBCA8030233D1BCAF030234BE02355390FA91E0FFA3E0FEA3E0FD75509075518D
:2031C400FA755200740E2FF553E43EF5548D558553828554838555F012628FFB855082858A
:2031E40051838552F0125F0690FA90E020E2030232B5758204C007C006C0051217C4D005C2
:20320400D006D00790FA90E0C423541FFB603CBB0F02806C8553828554838555F012628FD7
:20322400F582C007C006C005122622AB82D005D006D007BBFF0280488B82C007C006C005D5
:2032440012274DD005D006D007803574012FF553E43EF5548D558553828554838555F0C034
:2032640007C006C00512258FAB82D005D006D007BBFF02800B1224E578357401F20233CBCD
:20328400758204C007C006C0051217FDD005D006D0078F828E838DF0122DF7E582600B127C
:2032A40024E578357401F20233CB7835E4F20233CB74012FF553E43EF5548D558553508584
:2032C40054518555528550828551837408C0E07427C0E074F9C0E01245C415811581158128
:2032E4009213500B1224E578357401F20233CB758204C007C006C0051217C485538285542E
:20330400838555F012258FAC82758204C0041217FDD004D005D006D007BCFF028051C007D8
:20332400C006C005C0041224E5D004D005D006D0078F538E548D55AA53AB547411C0E0C042
:2033440002C00390F92F12463015811581158190F938E4F0A3F0A3F0A3F090FC63ECF090F9
:20336400FA90E054037824F28C82122781803D749AC0E0746BC0E0125A46158115818F8259
:203384008E838DF0122DF7E582601B1224E57810E2FB90FC63F090FA90E054037824F28BBA
:2033A4008212278180067835E4F2801B90F930E0FAA3E0FBC002C00390F9271244C6158131
:2033C400158178357401F27835E2F5822290F938E0FAA3E0FBA3E0FEA3E0FFEA4B4E4F70B9
:2033E4002F74012553FDE43554FEAF558D828E837408C0E07430C0E074F9C0E01245C41584
:203404008115811581921350071224E57582012290F91FE4F090FA91E0F550A3E0F551A3A2
:20342400E0F55274012550FAE43551FBC002C00390F9171244C615811581740D2550FDE4D4
:203444003551FEAF528D828E838FF012628F90F920F0AD50AE517411C0E0C005C00690F973
:203464002F12463015811581158174092550FDE43551FEAF528D828E838FF012628FFDA335
:2034840012628FFE8D828E83122853E582602790F938E4F0A3F0A3F0A3F07582041217C456
:2034A40090F0F775F0001213CB7582041217FD1224E575820122758200221224E5740E250B
:2034C40053FDE43554FEAF558D828E838FF012628FF58212045A758201227582041217C49E
:2034E4008553828554838555F0122C9AE582605B1224E57582041217C412356E300D111236
:203504003B03907019E4F5F01206C1125616805674A2C0E0746BC0E0125A461581158175D2
:2035240082041217FD90FA5B7401F090F0E374E0F0123B7C7408C0E0E4C0E0C0E090F92731
:20354400124598158115811581801B758200227F00C004C00774BFC0E0746BC0E0125A46F4
:20356400E58124FCF58175820022783AE4F208F208F208F2C20D7419C0E0E4C0E074A2C066
:20358400E074FDC0E0907000E4F5F01206E1E58124FCF58178367419F2747008F2E408F28F
:2035A40008F29062BD93FD740193FE7402937406C0E0C005C00690FDAB1245C4158115819B
:2035C40015819214500790F08F7405F0229062BDE493FD740193FE7402937409C0E0C005A6
:2035E400C00690FDAB1245C41581158115819214500790F08F7402F02290FDAAE0FFBF0182
:2036040002800790F08F7401F02290FDA8E0FEA3E0FFC3E49E74809F500790F08F7403F04D
:203624002290FDA8E0FEA3E0FF4E70030236E190FDA8E0FEA3E0FFC374239E74089F5004F9
:203644007E237F08C006C00774F3C0E074F0C0E07836E2F58208E2F58308E2F5F008E21272
:2036640006E1E58124FCF58190FA9474F3F074F0A3F0E4A3F090FA97EEF0EFA3F0783AE26B
:20368400F58208E2F58308E2F5F008E2C007C0061237BA783AC0E0E582F2E58308F2E5F022
:2036A40008F2D0E008F2D006D0078E028F037C007D007836E22AF208E23BF208E23CF208B7
:2036C400E23DF290FDA8E0FCA3E0FDECC39EFEED9FFF90FDA8EEF0EFA3F002362590FDA24D
:2036E400E0FCA3E0FDA3E0FEA3E0FF783AE2B5041108E2B5050C08E2B5060708E2B5070205
:20370400800790F08F7404F02290FDA6E0FEA3E0FF90F08DEEF0EFA3F0C006C00774DFC0E5
:20372400E0746BC0E0125A46E58124FCF581D20D221227A4AC82AD83AEF0FF90F941ECF0F9
:20374400EDA3F0EEA3F0EFA3F0227408C0E07450C0E074F9C0E090FA66124630158115818F
:20376400158190FA5DE054F84421F090FA5EE054F344C8F090FA607437F07413A3F090FA13
:203784006274FFF0A3F090FA647437F07413A3F0227411C0E0745DC0E074FAC0E090F9DCFF
:2037A40012463015811581158190F958E0FF04F090F9DEEFF022AC82AD83AEF0FF783EEC02
:2037C400F4F2EDF408F2EEF408F2EFF408F290FA94E0F55EA3E0F55FA3E0F56090FA97E06A
:2037E400F55AA3E0F55B855A5C855B5D155A74FFB55A02155BE55C455D70030238B8855E9D
:2038040082855F838560F012628FFEA385825E85835F8E047D007E007B00783EE26CF20870
:20382400E26DF208E26EF208E26BF27846E4F208F27846C3E2940808E26480948050A778D4
:203844003EE25401FA08E25400FB08E25400FC08E25400FE7842C3E49AF2E49B08F2E49C60
:2038640008F2E49E08F27841E2C313F56418E213F56318E213F56218E213F5617842E254E8
:2038840020FC08E25483FD08E254B8FE08E254EDFA783EEC6561F2ED656208F2EE6563086B
:2038A400F2EA656408F27846E22401F208E23400F2023835783EE2F4FB08E2F4FC08E2F4F0
:0B38C400FD08E2F48B828C838DF02263
:206AD8000A002530327820004150206F6E2025640A004E6F204150206F6E2025640A002EE8
:206AF80000636865636B435243206661696C65640A00524551202564207061727420256408
:206B18000044726177696E6720696D61676520696E20736C6F742025640A00626C6B206623
:206B380061696C65642076616C69646174696F6E210A0072657374617274696E6720696D8F
:206B58006720646C0A004368616E67696E67207374617274696E67536C6F742066726F6D9B
:206B780020256420746F20300A004E6F20736C6F74730A006E657720646C20746F202564F5
:206B98000A00646C20696D670A004F544120696D6167652076616C69646174696F6E2066C4
:206BB80061696C65640A00646174615479706520307825782069676E6F7265640A00436856
:206BD800726F6D613239004F54412076616C6964617465642C207570646174696E67207495
:0F6BF8006F2076657273696F6E202530780A0002
:05767200FF21474D4718
:2038CF00E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB9C
:2038EF0000438004002290F09B7438F0E4A3F030050990F09B7430F0E4A3F02290F959E05A
:20390F00704590F0C7E0603FC20490F09F7410F0C4A3F090F0A17406F0E4A3F09071D9751C
:20392F00F080120D72D20490F09F7418F07401A3F090F0A1740DF0E4A3F09071FB75F08014
:20394F00120D72D2038002C203300A2690F0C6E06020C20490F09F7410F0C4A3F090F0A1D4
:20396F007470F0E4A3F090720575F080120D72D20222C20222123EBD742CC0E07472C0E0C1
:20398F0012416C15811581743FC0E07472C0E012416C1581158190F09BE0FEA3E0FF740872
:2039AF002EFEE43FFF90F09BEEF0EFA3F07452C0E07472C0E012416C158115817475C0E0CF
:2039CF007472C0E012416C158115817497C0E07472C0E012416C1581158190F09BE0FEA3AF
:2039EF00E0FF74082EFEE43FFF90F09BEEF0EFA3F090F09DE4F0A3F07427C0E07472C0E04F
:203A0F0012416C15811581023A39758205123E9AE58260012274B5C0E07472C0E0125A4666
:203A2F0015811581903984020D4C7436C0E074FCC0E0E4C0E074C1C0E07472C0E012416CAB
:203A4F00E58124FBF581229062BBE493C0E0740193C0E074CDC0E07472C0E012416CE58142
:203A6F0024FCF58122123EBD123ED974DCC0E07472C0E012416C15811581123A5690F0E5E1
:203A8F00E0C0E0A3E0C0E074EDC0E07472C0E012416CE58124FCF58190FAB4E0FF3395E06D
:203AAF00FEC007C00674FBC0E07472C0E012416CE58124FCF581023A39758201123E9AE5E0
:203ACF00826001221219A2903A74020D4C123EBD123ED9C2051238F590F08DE0C0E0A3E020
:203AEF00C0E0740EC0E07473C0E012416CE58124FCF58122903ADC020D4C123EBD123ED95A
:203B0F00D2057424C0E07473C0E012416C15811581D20490F08FE0FFBF02028018BF040231
:203B2F008023BF05307435C0E07473C0E012416C15811581227444C0E07473C0E012416C64
:203B4F0015811581227455C0E07473C0E012416C15811581227E00C007C0067460C0E07418
:203B6F0073C0E012416CE58124FCF58122903B09020D4C123EBD123ED9746FC0E07473C0B7
:203B8F00E012416C158115817487C0E07473C0E012416C1581158190F09BE0FEA3E0FF9033
:203BAF00F0A1EEF0EFA3F090F94CE0FF7E0090F94DE0FD7C00C007C006C005C00474A0C0BA
:203BCF00E07473C0E012416CE58124FAF58190F94AE0FF7E0090F94BE0FD7C00C007C006CC
:203BEF00C005C00474B2C0E07473C0E012416CE58124FAF58190F948E0FF7E0090F949E047
:203C0F00FD7C00C007C006C005C00474B2C0E07473C0E012416CE58124FAF58190F946E051
:203C2F00FF7E0090F947E0FD7C00C007C006C005C00474B2C0E07473C0E012416CE5812423
:203C4F00FAF58190F099E0FEA3E0FF8E02EFFB3395E0FCFD7428C39AF50F74019BF510E45B
:203C6F009CF511E49DF512906F2DE493FDE4FC3395E0FBFAE50FC39DFDE5109CFCE5119B7F
:203C8F00FBE5129AFA78647402F2E408F208F208F28D828C838BF0EAC007C006125D95AABB
:203CAF0082AB83D006D00790F09FEA2EF0EB3FA3F090F0BFE0FF7E0090F0C0E0FD3395E053
:203CCF00FC90F959E0FB7A00C007C006C005C004C003C00274BBC0E07473C0E012416CE50D
:203CEF008124F8F581123F5A023A56758203123E9AE5826001221219A2903B82020D4C1210
:203D0F003EBD123ED974D5C0E07473C0E012416C1581158174E8C0E07473C0E012416C15BE
:203D2F0081158190F099E0FEA3E0FF90FA99EEF0EFA3F090F09BE0FEA3E0FF90F0A1EEF0E7
:203D4F00EFA3F074FDC0E07473C0E012416C1581158190FA99E0FEA3E0FF8E02EFFB33958A
:203D6F00E0FCFD74F0C39AFAE49BFBE49CFCE49DFD78647402F2E408F208F208F28A828B7F
:203D8F00838CF0EDC007C006125D95AA82AB83D006D007EA2EFEEB3FFF90F09FEEF0EFA3C2
:203DAF00F0EE5407601574075EFC7D00EEC39CFEEF9DFF90F09FEEF0EFA3F0906F2D75F00E
:203DCF0080120D7290F0A1E0FEA3E0FF74082EFEE43FFF90F0A1EEF0EFA3F0D2049070B76A
:203DEF0075F080120D7202390B758204123E9AE582600122903D0E020D4C123EBD123ED9BD
:203E0F0090F09BE0FEA3E0FF74102EFEE43FFF90F09BEEF0EFA3F07411C0E07474C0E0120C
:203E2F00416C15811581123F5A02390B758205123E9AE582600122903E09020D4C123EBD9A
:203E4F00123ED97421C0E07474C0E012416C158115817435C0E07474C0E012416C15811547
:203E6F008190F09F747CF0E4A3F090F09BE0FEA3E0FF90F0A1EEF0EFA3F0D2049070B7753E
:203E8F00F080020D72903E4C020D4CE582C40354F8F582122622AF82BFFF02800D7824E464
:203EAF00F28F821227817582012275820022C206C204C20590F099E4F0A3F090F09DF0A37E
:203ECF00F090F09BF0A3F0C20922D20490F0A1E4F0A3F0906DAA93FF7E007428C39FFF74D2
:203EEF00019EFE90F09FEFF0EEA3F0906DA975F080120D72C204906C08E493FF7E0090F03D
:203F0F009FE0FCA3E0FDECC39FFFED9EFE90F09FEFF0EEA3F0906C0775F080120D72906ECB
:203F2F006CE493FF7E0090F09FE0FCA3E0FDECC39FFFED9EFE90F09FEFF0EEA3F0D20490DC
:203F4F006E6B75F080120D72C2042290F0E9E0FEA3E0FF4E601190F0E7E0FCA3E0FDC3EE1F
:203F6F009CEF9D5002800890F0E7E0FEA3E0FFC006C007744FC0E07474C0E012416CE581CC
:053F8F0024FCF5812275
:206C0700801AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000E3
:206C270000000000000000000000000000000000000000000000000000000000000000004D
:206C47000000000000000000000000000000000000001C000000001F9F800000000038702B
:206C670001C07F000000001F9FC000000000387001C07F000000001F9FE000000000387021
:206C870001C0F7800000001C1CE000000000380001C0E3800000001C1CE0000000003800F1
:206CA70001C0E39DC1C7701F9CE773B871D03873B9C7E39FE3E7F81F9FEFF3FCF9F03873E7
:206CC700FDCEE39FE777F81F9FCFF3FDDDF03873FDDCE39CE7F7381C1F8E739DFDC0387366
:206CE7009DF8E39CE7F7381C1C0E739DFDC038739DF8F79CE707381C1C0E739DC1C03873D5
:206D07009DDC7F1FE7F7381F9C0FF3FDFDC03F739DDC7F1FE3E7381F9C0FF3FCF9C03F73D9
:206D27009DCE1E1DC1C7381F9C0773B871C03F739DC7001C00000000000003800000000013
:206D47000000001C0000000000000380000000000000001C000000000000038000000000EE
:206D67000000001C000000000000038000000000000000000000000000000000000000006D
:206D8700000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFA
:206DA700FFFF800C000882A2AB55BFFFFFFF7DB4622800000010101176FF7BFFFF7FB7A9A1
:206DC70055505100002922966FF7BFFFFFFFEAE622AA08000002542ABF3F7FFFFFFFFD59BF
:206DE700B4102000002423B5B5DFFFFFFFFFF6AE48551000008890546AEFFFFFFFFFFBF51A
:206E0700A382000000012DA3B3FFFFFFFFFFF6BE4E5209000490020D55DFFFFFFFFFFDDDBD
:206E270068A9001000490CFBAB6FFFFFFFFFFEEB955A002000129122BFDFFFFFFFFFFFF579
:206E4700658140400028127DEFFFFFFFFFFFFFEFAB28000000420A9775FFFFFFFFFFFFF71A
:206E67004AC20400800C0008029CDFFFFFFFFFFFFF6A6A8D002000044D72AAFFFFFFFFFF07
:206E8700FFD5D57480000240128EDFFDFFFFFFFFFFAEAA0A11200020525FF57FFFFFFFFFC1
:206EA700FF7294AA240000010AAB6F77FFFFFFFFFDAF545488000004804D95EBFFFFFFFF37
:206EC7005B58B5514000000808A6B3F77FFFFFFFFFAD4AA0100000029641DCAEFFFFFFFFD1
:206EE700FDBFEA4802000000093EAB75EFFFFFFFFFD4B121080000000AC35DFF7FFFFFFFF6
:206F0700EFEF444A64800000A152A6EFEFFFFFFFD6CAF5101800000002154BD5DFFFFFF7DE
:206F2700EBD4D88500003838000000000000000000000000000000000000000000000000BE
:206F47000000000000000000000000000000000007C00000000001FFF0000000000FFFF075
:206F6700000000007FFFF000000001FFFFF000000007FFFFC00000001FFF80000000007FCB
:206F8700F80000000000FFE00000000001FF000000000007FE00000000000FF80000000007
:206FA700001FE000000000003FC00003E000007F80007FF00000FE0003FFF00001FC000F7F
:206FC700FFF00001F8003FFFF00003F800FFFF800007F001FFE000000FE003FF0000000F44
:206FE700C00FF80000001F801FF00000001F803FC00000003F003F800000003F007F0000BB
:2070070000007E00FE0007E0007E01FC001FF000FC01F8007FF000FC03F001FFF000F8033E
:20702700F003FFF001F807E007FF0001F807E00FF00001F00FC01FE00001F00FC03F800064
:2070470003F00F803F000003F01F807E000003E01F807E000003E01F00FC01E003E01F0077
:20706700FC07F803E01F00F80FFC03E03F00F80FFC03E03F01F81FFE03E03F01F81FFE0371
:20708700E03F01F81FFE03C03E01F01FFE01C01E00F00FFC00000000000FFC0000000000C0
:2070A70007F8000000000001E000000000000000303000001FF800000000FFFF0000000371
:2070C700FFFFE000000FFFFFF000003FFFFFFC00007FF00FFE0000FF8001FF0001FE00009B
:2070E7007F8003F800001FC007F000003FE00FE000007FF00FC00000FFF01F800001FFF8E7
:207107001F000003FFF83F000007FEFC3E00000FFC7C7E00001FF87E7C00003FF03E7C00D2
:20712700007FE03EFC0000FFC03FF80001FF801FF80003FF001FF80007FE001FF8000FFCE2
:20714700001FF8001FF8001FF8003FF0001FF8007FE0001FF800FFC0001FFC01FF80003F8E
:207167007C03FF00003E7C07FE00003E7E0FFC00007E3E1FF800007C3F3FF00000FC3F7F8D
:20718700E00000FC1FFFC00001F80FFF800003F00FFF000007F007FE00000FE003FC0000BC
:2071A7001FC001FE00007F8000FF8001FF00007FF00FFE00003FFFFFFC00000FFFFFF000BA
:2071C7000003FFFFE0000000FFFF000000001FF800001010000000000000000000007C0016
:2071E7008200000038004407007903811C0103810079000708080063773E1C3E77631010E9
:20720700000003C00FF0081008100810081008100810081008100BD00BD008100FF0000018
:207227000A000A0A0009084F70656E4550617065724C696E6B0A08006F70656E6570617051
:2072470065726C696E6B2E64650A0049276D0920666173742061736C656570202E202E2097
:207267002E20746F2077616B65206D653A000A52656D6F7665206261747465726965732C5B
:207287002073686F727420626174746572790A00636F6E74616374732C207265696E736541
:2072A7007274206261747465726965732E004465657020736C6565700A00546167204D4145
:2072C700433A202573004368726F6D613239207625303458005374617274696E67202E207C
:2072E7002E202E0A0A000A564261743A202564206D560A0054656D70657261747572653AE8
:20730700202564430A0A000C466C617368696E67207625303478202E202E202E000C4F54FE
:2073270041204641494C4544203A280A0A000C4E6F74204F544120696D616765000C577271
:207347006F6E67204F544120696D616765000C435243206572726F72000C4572726F722057
:20736700436F64652025640057616974696E6720666F722064617461202E202E202E0A00FA
:207387000A466F756E642074686520666F6C6C6F77696E672041503A000A4150204D414317
:2073A7003A202530325825303258002530325825303258000A43683A2025642052535349F7
:2073C7003A202564204C51493A2025640A00084E6F20415020666F756E64203A280A080A80
:2073E700005765276C6C2074727920616761696E20696E2061000A6C6974746C652077681D
:20740700696C65202E202E202E00087A7A5A5A5A202E202E202E0A080A000C454550524FAA
:207427004D204641494C4544203A280A0A000C536C656570696E6720666F72657665722021
:157447002E202E202E0A0A00564261743A202564206D560A0015
:203F9400C007C006C005C004C00390FAA2E0600302408890FAA3E0FF24FB5003024085EFC7
:203FB4002F2F903FBA73023FC9023FE702406802407F02408590FAA0744BF0E4A3F090FA55
:203FD400A2740BF090FAA37401F090FAA47468F002408890FA9D1248B0E582FF6045EF2417
:203FF400E0FFBF600040027F1F90FAA5E0FE0E90FAA5EEF0C0078E821243DAAD82AE83152C
:204014008190FAA4E0FC7B002DFDEB3EFE8D828E837467C0E01248ABAE82158190FAA4EEB3
:20403400F0800B90FAA37402F090FAA4E0FFEF75F002A4245CF582747435F0F583E493FE66
:20405400A3E493FF90FAA0EEF0EFA3F090FAA2740BF0802090FAA37403F090FAA074E3F069
:20407400741AA3F090FAA203F0800990FAA37404F0C3802D90FAA0E0FEA3E0FFEE5401246D
:20409400FFE433FDEFC313CE13CEFF90FAA0EEF0EFA3F090FAA2E0FF1F90FAA2EFF0ED24B6
:0C40B400FFD003D004D005D006D00722B6
:20745C009B01B3013303C90089019101990019013101930013012301CD01D90199039D0113
:20747C00B90139037302D30193033B017301B7039701A7012703370167016702DB001B0345
:20749C006303C500D10011038D00B10031028B00A3002302ED008D03B103DD001D0371035A
:2074BC0077038B03A303BB003B02BB03D70017034703B7003702C702F70213028F02650054
:2074DC00850169000903A10121034D000D01590019026101610243025300EF024301F1027B
:2074FC00E501E901C9033D01790179022F014F014F02DB037B036F03F500C503D103BD00B4
:0E751C003D02AF002F02DD03BD03D703AF0316
:2040C000C007C006C005C004C001C000AC82AD83AEF0FFBF20005031BF0A0302415F8F826F
:2040E000C007C006C005C004C003C002C001C000C0251242A7D025D000D001D002D003D0B9
:2041000004D005D006D00780567E00EF24E0FFEE34FFFEEF2FFFEE33FEEF2498F582EE3434
:2041200063F583E493A3E493C4540FFE90F0B3EEF0300507EE2EFE90F0B3F090F0B3E0FF52
:204140003395E0FE0FBF00010E90F099E0FCA3E0FDEF2CFFEE3DFE90F099EFF0EEA3F0D0DB
:2041600000D001D004D005D006D00722C007C006C005C004C003C001C000E58124F6FFC05D
:2041800007C006C005C004C003C002C001C000C02512425AD025D000D001D002D003D00421
:2041A000D005D006D007E58124F6F886050886068D828E83E493FCBC0C7290F099E4F0A389
:2041C000F0C007C005C006C0E0C0E09040C01256F5E58124FBF58190F099E0FDA3E0FE33CB
:2041E00095E0FCFB7428C39DFD74019EFEE49CFCE49BFB78647402F2E408F208F208F28DB0
:20420000828E838CF0EBC007C002C001C000C025125D95AB82AC83ADF0FED025D000D00124
:20422000D002D00790F099EBF0ECA3F0C007E58124F5F8E6C0E008E6C0E0E4C0E0C0E0905C
:20424000109A1256F5E58124FBF581D000D001D003D004D005D006D00722300625300506DA
:204260007E147F0080047E0A7F0090F0B4EEF03005067E207F0080047E107F0090F0B3EE86
:20428000F0223005067E207F0080047E107F0090F0B4EEF03005067E147F0080047E0A7F3A
:2042A0000090F0B3EEF022AF82BF08028018BF0902804EBF0A028013BF0B028055BF0C01D6
:2042C00022BF0D568038B20502425A12425A90F0B4E0FE3395E0FD90F09BE0FBA3E0FCEEC5
:2042E0002BFEED3CFD90F09BEEF0EDA3F090F09DE0FDA3E0FE90F099EDF0EEA3F022B2042C
:204300002290F099E0FDA3E0FE90F09DEDF0EEA3F02290F09DE4F0A3F0227E00C007C006B6
:12432000742AC0E07475C0E0125A46E58124FCF58122F4
:15752A00496E76616C69642063686172203078253032780A00F6
:20433200E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB2E
:2043520000438004002275FB02228582F9E5F830E1FB53F8FDE5F820E0FB85F9822253F35D
:20437200C775FA2275FC0C75F8C043F4C043F102D20B2253F1FD53F43F75FA0075FC3175B0
:08439200F84043F338C20B228E
:20439A00858283D0F0D0E0D082C082C0E0C0F022C00074FB2581F8E5828682088683088628
:2043BA00F0D0002275F008C58213C58233D5F0F7C58222E583A2E713F583E58213F582220C
:2043DA00D083D0E0D0F0C0F0C0E0C083E582A4F58285F08322C000E58124FDF8E6F88582AD
:2043FA00F0A4F582E8A8F08583F0A428F583E5F03400F5F0E4D00022C000C001C0E0C0F047
:20441A0074FA2581F88600E58288F0A4F582A9F0E58388F0A429F583E5F03400F9D0E088FE
:20443A00F0A429C5F03400F9D0E0C0F088F0A429D0F0D001D00022C000C001C002E58124CE
:20445A00FBF8E6F918E6F88582F0A4C0E0AAF0E58388F0A42AF8E5F03400FAE98582F0A478
:20447A0028C0E0E5F03AFA74003400F8E98583F0A42AFAE5F0388AF0D083D082D002D00139
:20449A00D00022D0E0D0F0C000C001C002C0F0C0E074F82581F9E7F809E7F922D002D00175
:2044BA00D00022C9C583C9C8C582C82212449D75F0081244BDE0A31244BDF0A3D5F0F302C7
:2044DA0044B612449D75F0081244BDE493A31244BDF0A3D5F0F20244B612449DC375F008BF
:2044FA001244BDE0A3F5021244BDE03AF0A3D5F0EF0244B612449D75F0081244BDE0A3F5BA
:20451A00021244BDE05AF0A3D5F0EF0244B612449DC375F0081244BDE0A3F5021244BDE04C
:20453A009AF0A3D5F0EF0244B612449DC375F0081244BDE0A3F5021244BDE0A39AD5F0F0EF
:20455A000244B612449D75F0081244BDE0A3F5021244BDE0A36A7007D5F0EED30244B6C39C
:20457A000244B6C8C0E075F008E038F0A3D5F0F9D00022D3E402457DC374FF02457DC000C0
:20459A00C00174FA2581F8E6F5F0088601496015088600E8F0A374FF25F0F5F074FF39F505
:2045BA000145F070EED001D00022C000C001C00274F92581F9E7D3601CF5F0098700098700
:2045DA0001C3E0F502A31244BDE0A31244BD6A7004D5F0EED3D002D001D00022C000C00160
:2045FA00C00274F82581F9E7F5F00987024A601F0987000987011244BDE0A31244BDF0A350
:20461A0074FF25F0F5F074FF3AF50245F070E7D002D001D00022C000C001C00274F92581F8
:20463A00F9E76015F5F00987000987011244BDE0A31244BDF0A3D5F0F3D002D001D000227C
:20465A00C083C082E06003A380FAC3E582D0F095F0F582E583D0F095F0F58322C003C002A9
:20467A00C001C000C0E0C0F0C083C082A98174F62581F886F018860075020175030075829D
:20469A0011A2F74014C3E833F8E5F033F5F0C3EA33FAEB33FB058280E8E4C003C002C0E054
:2046BA00C0E0AA81C0F0C000C0E0C0E0A881C0E0C0E0C0E0C0E0AB81C000C001C375830450
:2046DA0086F018E71995F0D583F6D001D000402BC000C001C375830486F018E795F0F71909
:2046FA00D583F585020085030175830486F018E745F0F719D583F5D001D00074FC28F8C34C
:20471A0075830408E613F6D583F9C00074FC2AF8C375830408E613F6D583F9D000D5829823
:20473A0020D508D082D083D0F0D00074F42581F58130D508D082D083D0F0D000E8D000D0DF
:20475A0001D002D00322C2D5024676D2D5024676C003C002C001C000C0E0C0F0C083C082E2
:20477A00A98174F62581F886F07401758219A2F7400A23C5F023C5F0058280F2C0E0E4C022
:20479A00E0C0E0C0E0AA81C0F0C0E0C0E0C0E0A881C0E0C0E0C0E0C0E0AB81C000C001C30B
:2047BA0075830486F018E71995F0D583F6D001D000402BC000C001C375830486F018E7952C
:2047DA00F0F719D583F585020085030175830486F018E745F0F719D583F5D001D00074FC4E
:2047FA0028F8C375830408E613F6D583F9C00074FC2AF8C375830408E613F6D583F9D0004F
:20481A00D58298D082D083D0F0D00074F42581F581E8D000D001D002D00322C006C005C03B
:20483A0004C003C002C001C00074F72581F886F074017C09A2F7400923C5F023C5F00C80BD
:20485A00F3FBE4FAA9F0F88582F0FDFEC3E58298E583994011C3E58298F582E58399F58329
:20487A00EA4DFDEB4EFEC3E913F9E813F8C3EB13FBEA13FADCD620D5048D828E83D000D0EA
:20489A0001D002D003D004D005D00622C2D5024835D2D5024835E0C0E02401F0A3E0C0E0C3
:2048BA003400F0A3E0D083D0826014B4600AC000A882E2D000F582224009E493F58222E092
:2048DA00F58222C000A882E6D000F5822212449D75F004D31244BDE0FA3400F0EAA31244C9
:2048FA00BDF0A3D5F0EE0244B612449D75F004C31244BDE0FAA31244BDE0A36A7004D5F0B7
:20491A00EFD30244B612449DC3E0A398E0A399E0A39400E0A394000244B612449DC3E0A36F
:20493A0038E0A339E0A33400E0A334000244B675F004E07004A3D5F0F9F58222A3A3A3E07F
:02495A00332206
:04495C0075C90B22EC
:20496000C0E0C007C006C0D075D000AEDAAFDB74012EF5DAE43FF5DBD0D0D006D007D0E0C1
:2049800032C007C006C005C004C000AEDAAFDB784AEEF208EFF27848E5E2F27849E5E3F27E
:2049A000EEB5DAE7EFB5DBE37848E2FC08E2FD08E2FE08E28C828D838EF0D000D004D005C5
:2049C000D006D0072285E28222E4F5DAF5DBF5E4F5E5F5E6F5E7F5E275E40543D84043B885
:2049E0000222AF82AE83ADF0FC784CEFF2EE08F2ED08F2EC08F21249817850C0E0E582F2A1
:204A0000E58308F2E5F008F2D0E008F2124981AA82AB83AEF0FF7850D3E29AF4B3FAB30875
:204A2000E29BF4B3FBB308E29EF4B3FEB308E29FF4FF784CC3E29A08E29B08E29E08E29FAF
:034A400050CA2237
:204A430053C07F53A1FE53BEFC3243870122AF82AE83ADF0FC7854EFF2EE08F2ED08F2EC40
:204A630008F290FAA6E4F090FAA7F090FAA874DFF090FAA974BEF090FAAAE054E0F090FA88
:204A8300AAE0541FF090FAAB7407F090FAACE054E0F090FAACE0549F4420F090FAACE05485
:204AA3007FF090FAADE054FC4402F090FAADE054FBF090FAADE054F7F090FAADE054CFF015
:204AC30090FAADE0543F4440F07854E2F5F008E242F008E242F008E245F0701B7B0175133C
:204AE3006F7514FC85141590FAA6E515F07A6F90FAA7EAF0024B857854C3E2943F08E2946F
:204B03000008E2940008E29400500C7854743FF2E408F208F208F27854E225E0F51608E24F
:204B230033F51708E233F51808E233F519E5162516F516E51733F517E51833F518E51933F4
:204B4300F519747DC0E0E4C0E08516828517838518F0E5191247607854C0E0E582F2E58382
:204B630008F2E5F008F2D0E008F2158115817B007D687EFC8E0590FAA6EDF07E6890FAA702
:204B8300EEF053C07F53A1FE43A11075A8A0EB70147854E2F5F008E242F008E242F008E2DB
:204BA30045F070012253BEFBE5BE30E5FBE5C654384441F5C6E5C630E6FB43BE0453C67F9B
:204BC3007854C3E2F5F074F095F008E2F5F074FF95F008E2F5F0E495F008E2F5F0E495F05C
:204BE300501B7DF07EFF7854E22410F208E23400F208E234FFF208E234FFF280117854E21C
:204C0300FD08E2FE7854E4F208F208F208F2EB60067A007C0080047A107C008AA175C000EB
:204C230043D6817AA67CFA8AD48CD575D60175A204E5A570FC75A2028EA48DA3AEA5EEB5B4
:204C4300A50280FA43C702EB60077B0075BE07800375BE06000000E5BE5403600875D701B2
:204C6300004387010053C7FDE5BE30E5FB75BE00E5BE30E5FB75C679E5BE30E6FB7E007D53
:1B4C830000000DBD80FB0EBE80F575C639E5C620E6FB75C63875BE04024B91E8
:0E7677000606060606060407070707070704AF
:204C9E00AF8275BB0074304FF5B575B473E5B420E6FBE5B430E7FBAEBA7F00ADBB7C0090BC
:204CBE00FAB2EC4EF0ED4FA3F08FB522C21590FAB2E0FEA3E08E82C4C582C4540F6582C569
:204CDE0082540FC5826582C582F58374E2C0E07404C0E0124451AC82AD83AEF0FF15811589
:204CFE008190FAB0E0FAA3E0FB74FFC0E07407C0E08A828B83124451A882A983AAF0FB1594
:204D1E00811581ECC398FCED99FDEE9AFEEF9BFF747DC0E08C828D838EF0EF124412AC82D7
:204D3E00AD83AEF0FF1581EFC40354F8CEC403CE6ECE54F8CE6EFFEDC40354074EFEEDC4BE
:204D5E000354F8CCC403CC6CCC54F8CC6CFDEF30E70FD215C3E49CFCE49DFDE49EFEE49F12
:204D7E00FF90FAAEE0C0E0A3E0C0E08C828D838EF0EF124760AC82AD83AEF0FF15811581D0
:204D9E0074FF2CFC74033DFDE43EFEE43FFF74FFC0E07407C0E08C828D838EF0EF124760F5
:164DBE00AC821581158190FAB4ECF030150790FAB4C3E49CF0228C
:204DD400E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB82
:204DF400004380040022C007C006AF82BF64004027BF6A00502290DF097421F090DF0A74E9
:204E14003BF090DF0B7413F08F06EE249CFE90DF0675F003A4F0802F90DF097422F090DF94
:204E34000A74BBF090DF0B7413F0BFC8004013BFE100500EEF2438FF90DF0675F00CA4F0A8
:204E5400800590DF06E4F0D006D00722C00775E10490DF3BE0B401FCD00722124E607F000D
:204E74008F057EDFEFC390753F938D828E83F00FBF200040EB90DF237488F090DF247431C5
:204E9400F090DF257409F090DF2E748EF075911075ED00759D0075EF0322C007AF82C3EFC1
:204EB4006480946250047FE2800EC3748A8FF063F08095F050027F0A741E2FC31390755F53
:204ED4009390DF2EF0D00722C007C006AE82AF838ED48FD575D601000000000000000000A4
:204EF400D006D0072275D68100000000000000000022C007C006C005AE82AF83124EF98F45
:204F14000590FC78EDF090FC79EEF07E767FFC8E828F83124EDCD005D006D00722C007C0BC
:204F340006C005AE82AF83124EF98F0590FC7EEDF090FC7FEEF07E7E7FFC8E828F83124E7A
:204F5400DCD005D006D00722301604E5EF700122124E6053C0FE43B801E5ED75F080A424C0
:204F7400B6F58274FA35F0F583124F0675E1022253B8FE124EF9124E6075D10053C0FE2269
:204F9400AE82AF837CFF7DFF124F848E828F83124F3175E90075E103E5E920E42375820F59
:204FB400C005C004124C9ED004D00590FAB2E0FEA3E0FFC3EE9CEF9D50DE8E048F0580D88E
:204FD400124EF9C005C004124F5CD004D0057423C0E08C828D831248A615817404C0E01260
:204FF40043EFAC82AD83158190F0E7ECF0EDA3F022C025C0E0C0F0C082C083C007C006C08B
:2050140005C004C003C002C001C000C0D075D000E5D130E040E5ED75F080A424B6FE74FA31
:2050340035F0FF53D1FE8E828F83E0FD248240200D0DED2EF582E43FF583E0FF30E711E5DE
:20505400ED04FF8FEDBF030375ED00E5EF14F5EF124F5C800F7474C0E07475C0E0125A46CE
:205074001581158153C0FED0D0D000D001D002D003D004D005D006D007D083D082D0F0D06E
:20509400E0D02532201806124F84C21622201605D216024F5C22E59D75F080A424B6FE748F
:2050B400FA35F0FF8E828F83E0FD7403B5EF047582FF228D037C0074022BF9E43CFAE92EB1
:2050D400F582EA3FF583E0FA747F5AD39494F4FABA7F0040047AFF8003EA2AFA90F0BFEAE4
:2050F400F00BBB00010CEB2EF582EC3FF583E0A2E71324B390F0C0F00EBE00010FC005C0C2
:2051140006C00790F95B124630158115811581E59D04FF8F9DBF0303759D00D21910AF024C
:20513400C219E5EFFF04F5EFA21992AFEF7007C005124F5CD0058D8222D21A10AF02C21A52
:10515400AFEF75EF03A21A92AFEF7003024F5C2218
:20753F00D3917D044522000F0022BB131D3B1373A7650700181E1CC700B0B610EA2A001F2E
:20755F00030506080B0E191C242527293855408D8A84CAC4C0646D612069727120756E6553
:09757F00787065637465640A000C
:10768500DFD900008080131A0000DFD9207E134265
:1700E10090FC477456F090FC487412F090FC4903F090FC4A7485F0AA
:20516400E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FBEE
:20518400004380040022C007C006C005C004C003C002C001C000E582785BF27E00C21C7806
:2051A4005CE4F2C006785CE2C0E09010001243EFAA82AB831581785DEAF2EB08F2785F74E8
:2051C40004F2E408F2785DE2FA08E2FBE4FCFE7404C0E0E4C0E0744BC0E074FCC0E08A826C
:2051E4008B838CF0EE1206E1E58124FCF5817404C0E07447C0E074FCC0E090FC4B1245C4C9
:20520400158115811581921DD006201D030252EE785FE2FB08E2FCC3941040030252EEC07B
:2052240006785D795FE3C5F0E225F0FB09E3C5F008E235F0FAE4FCFE7402C0E0E4C0E07497
:205244004BC0E074FCC0E08B828A838CF0EE1206E1E58124FCF58190FC4BE0F51A74FFB5E8
:205264001A05D0060252EED006785BE2B51A5D90FC4CE0FCBC020040531C1C8C06785AC3D8
:20528400E29C5004785AE2FCC0067B007858E2F51B08E2F51C785DE2FD08E2FF74022DFD4D
:2052A400E43FFF785FE22DFD08E23FFA7E007F00C004C003C01BC01C8D828A838EF0EF12EC
:2052C40006E1E58124FCF581D21CD00690FC4CE0FF7D00785FE2FB08E2FCEF2BFBED3CFC1B
:2052E400785FEBF2EC08F2025214785CE22401F2785CE2B40A0050030251A7301C048E0735
:2053040080027FFF8F82D000D001D002D003D004D005D006D00722758204C007C006C0056D
:20532400C004C003C002C001C0001217C412539CD000D001D002D003D004D005D006D00715
:20534400301B26C007C006C005C004C003C002C001C0001216E912539CD000D001D002D067
:2053640003D004D005D006D007758204C007C006C005C004C003C002C001C0001217FD751E
:205384008200125C24D000D001D002D003D004D005D006D007D2AF22D21B785874A2F2747D
:2053A400FD08F2785A7407F275822A12518AE582FF30E71B785874A2F274FD08F2785A7484
:2053C40006F275820112518AE582FF30E701227404C0E0E4C0E0C0E090FDB212459815814C
:2053E400158115817404C0E074B2C0E074FDC0E090FDA41245C4158115811581921E501D63
:205404007404C0E074B2C0E074FDC0E0906A721245C4158115811581921E40012290F9575D
:205424007444F090F9567467F090FDA4E0FF90F953F090FDA5E0FE90F952F090FDA6E090BC
:20544400F951F090FDA7E090F950F090FDA4E4F07D007C00C006C005C007C00474A2C0E067
:2054640074FDC0E0E4C0E07488C0E07475C0E0125A46E58124F7F58190F950E0FF7E0090FF
:20548400F951E0FD7C00C007C006C005C0047496C0E07475C0E0125A46E58124FAF58190E0
:2054A400FDA2E024BF90F955F090FDA3E024BF90F954F090F956E0FF7E0090F957E0FD7C83
:2054C40000C007C006C005C00474A0C0E07475C0E07436C0E074FCC0E0125ABFE58124F86E
:2054E400F58190F954E0FF7E0090F955E0FD7C00C007C006C005C00474A0C0E07475C0E06E
:20550400743AC0E074FCC0E0125ABFE58124F8F58190F952E0FF7E0090F953E0FD7C00C0D9
:2055240007C006C005C00474A0C0E07475C0E0743EC0E074FCC0E0125ABFE58124F8F5814F
:2055440090F950E0FF7E0090F951E0FD7C00C007C006C005C00474A0C0E07475C0E07442D5
:20556400C0E074FCC0E0125ABFE58124F8F581785874AEF274FA08F2785A7402F2758212C5
:2055840012518AE58230E70122785874B0F274FA08F2785A7402F275820912518AE582306D
:2055A400E70122C21B229055B57416C39582F5F02275F020C2ADC2AC43AE0100E5AE20E7EB
:2055C400FB05AD05ADD5F0F09000008583AD8582ACA375AB2275F00275F900E5F830E1FB18
:2055E40053F8FDE5F820E0FBC0F9D5F0EB43AE02E5AE20E6FBD0E0D0AFF5AFE5AE20E6FB30
:2056040053AEFDE5AE20E7FBE583B440BE75C90B80FEC007C006C005C004C0031255AAAC7C
:2056240082AD83AEF0FF8E03C003C004C00590FDA212463015811581158175D6FF75A8005A
:2056440075C70390FDA2E473D003D004D005D006D00722C007C00675A80075B800759A0050
:2056640075C70075BE00E5BE30E5FB75C679E5BE30E6FB7F007E00000EBE80FB0FBF80F515
:2056840075C639E5C620E6FB75C63875BE04D006D0072243B404E5B4540C600280F8E5BCFE
:2056A40065BDF58222125697C082125697C082125697C082125697D083D0F0D0E022C000C4
:0B56C4008582BC7861E2F5BCD00022BA
:20758800534E20257325303278253032780025303278253032780A002530325825303258C6
:0175A80000E2
:2056CF00E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB7E
:2056EF00004380040022C007C083C082858107C004C003C002C001C00074F42581F8868380
:20570F0018868218860018E0A36013B4250B7C007A00E0A36008B425161259D780E9D0007F
:20572F00D001D002D003D004D082D083D00722B4630518E61880E2FB4420B46D3AEB5420C5
:20574F0003F9C083C0821259737C0874FF2C93C083C082FAC47B02540F29905A06931259EB
:20576F00D7EADBF3D082D083DC06D082D0838097743A1259D780D4EBB42A09BA009B740168
:20578F004CFC808EB43007BA0004740680F2FB24D0501224F6400E240AFB740A8AF0A42B66
:2057AF00FA740480DBBB6C0DEC54086004741080CF740880CBBB733CEC5408C083C08270EC
:2057CF000512597A8003125973EC54047009E0A360981259D780F7BA0002808EE0A3600AC7
:2057EF001259D7DAF780830258C774201259D7DAF9025779EB4420B478EDC083C082125990
:20580F0090C083C082E5F023F90324FF2582F582E5833400F583E054F0701119E0700D19E7
:20582F001582E582B4FF021583D5F0EAB9000109EC5420600B09EC54026005742D1259D73E
:20584F00EC54046018EAC39940136011FAEC5402C4032420F582E5821259D7DAF9EC5420D9
:20586F00600B19EC54027005742D1259D7EB542003FAD082D083E904C313FC142582F58209
:20588F00E5833400F583E954016005E079018005E07902FBC4540F2AC083C082905A0693B4
:2058AF00D082D0831259D7EBD9EB1582E582B4FF021583DCDB025779BB640674204CFC80EE
:2058CF0008BB75028003025716C083C082125990C0F07408C395F0244FF9E434FCFBE0A39B
:2058EF001259CCF0A31259CCD5F0F31259CCF5F0EC54206028E5F054807006EC54DFFC8022
:20590F001CD0F0C0F07408C395F0244FF582E434FCF583C3E0F9E499F0A3D5F0F775F00ADB
:20592F00E4F0A3D5F0FBD0E0C403FB90FC5775F00AE0F9540F24FBE950022403F954F0243F
:20594F00B0E950022430F0A3D5F0E690FC4FC375F012E033F0A3D5F0F9DBD090FC5775F04F
:20596F000A02581086831886821822E61860F5648060F164C0600585938380EA7583FF80B4
:20598F00E5EC5401701F88F07583FFEC5418600A5410600418181818181818888218E5F0AB
:2059AF00C398F5F022EC5418600E5410600575F00880B175F00480AC75F00280A7C583CB13
:2059CF00C583C582C9C58222C083C082C001C0008F008682188683181818188601188600B4
:2059EF001259FBD000D001D082D08322C082C083850082850183223031323334353637383F
:205A0F003941424344454630313233343536373839616263646566C007C006C005C004FF32
:205A2F00BF0A0675820D12435C8F8212435CD004D005D006D00722C007C001C000A20B9212
:205A4F001FE58124FAFF200B03124370C007E58124F9F8E6C0E008E6C0E0E4C0E0C0E09098
:205A6F005A261256F5E58124FBF581201F03124385D000D001D00722C007C006C005C00473
:205A8F00C003C001AC82FF8C018F0487060987071974012EFBE43FFDA70309A7058E828F2D
:205AAF0083ECF0D001D003D004D005D006D00722C007C006C005C001C000E58124F6FFE525
:205ACF008124F8FE7D00C007E58124F5F8E6C0E008E6C0E0C006C005905A871256F5E5818E
:195AEF0024FBF58124F8F88682088683E4F0D000D001D005D006D00722C3
:0200F800C22024
:205B0800E58620E0FB225390FD00225380FB0022E58620E0FB004390020022E58620E0FB40
:205B2800004380040022C007AF82E58620E0FB53807F008F82125CAB00E58620E0FB438071
:205B480080D0072253BEFC32AE82AF838E828F83E493FD74012EFBE43FFCEDFA6052539054
:205B6800FD008B828C83E493FD0BBB00010CE58620E0FB53807F008D82125CAB00E5862052
:205B8800E0FB438080EA14FDED6015E58620E0FB8B828C83E493F5C1A3AB82AC831D80E84F
:205BA8008B068C07E58620E0FB0043900200809C2230200122D220758201125C244390027C
:205BC8005380BF43F42875C20075C5317586009000FDE4F5F01249E24390049000FDE4F55F
:205BE800F01249E22090FD9075A9125B505390FD00E58620E0FB53807F00758204125CABAC
:205C080000E58620E0FB4380803090FDE58620E0FB00439002009075B5025B50E582601697
:205C280090F953E054F0600122438FC143FDC143F62E43FE2E2253F4D790F953E0FF54F030
:205C4800600B53807E5390D100D2860022538F3A53FD3A53F6D153FED122202001229076E5
:205C680041125B50758200125C24C22022125BB95390FD003021067E137F0080047E107F93
:205C880000E58620E0FB53807F008E82125CAB00E58620E0FB438080E58620E0FB00439039
:205CA800020022C007AF82E58620E0FB8FC1D007227EFF7FFF744EC0E07476C0E0125A4678
:205CC8001581158190764BC007C006125B50D006D00720902F900032E4F5F0C007C006123F
:205CE8004A5112565775820F124C9ED006D00790FAB2E0FCA3E0FDC3EC9EED9F50D48C066C
:205D08008D0780CE7423C0E08E828F831248A615817404C0E01243EFAC82AD83158190F0DA
:205D2800E9ECF0EDA3F07582011217C4125C621219A27460C0E07476C0E0125A4615811539
:025D48008122B6
:2075A900050107000900040607070F0002008F046180012802302902501702820802602212
:2075C900102001010103040906060A040419030409102101010103840986460A844419039A
:2075E9004409102201010143040986460A84441983040910250A0A0102140D1414010000D3
:207609000000000010264A4A0182540D54540100000000000010270A0A0102140D14140172
:20762900000000000000102401810183840986460A84441903040900028200050102000027
:2076490000000112005570646174696E6720646973706C61790A0055706461746520636F5D
:087669006D706C6574650A0088
:205D4A00AE82AF837C007D007B10EF235401FAEE2EFEEF33FFEC2CFCED33FDEA60034304F2
:205D6A00017862C3E2F5F0EC95F008E2F5F0ED95F040117862D3E29CF4B3FCB308E29DF4B5
:0B5D8A00FD430601DBC48E828F8322E4
:205D9500AC82AD83AEF0FF33E433FB6013C3E49CF556E49DF557E49EF558E49FF5598008B8
:205DB5008C568D578E588F59AC56AD57AE58AF597867E233E433FA60207864E2D3F43400E8
:205DD500F55608E2F43400F55708E2F43400F55808E2F43400F55980117864E2F55608E2C2
:205DF500F55708E2F55808E2F559786CE556F2E55708F2E55808F2E55908F28C828D838E71
:205E1500F0EFC003C002125E697868C0E0E582F2E58308F2E5F008F2D0E008F2D002D003D7
:205E3500EA6B60207868E2D3F43400FA08E2F43400FB08E2F43400FE08E2F434008A828BFB
:145E5500838EF0227868E2F58208E2F58308E2F5F008E222A0
:205E6900AF82AE83ADF0FC7870EFF2EE08F2ED08F2EC08F27A007B007E007F007D20787326
:205E8900E2235401FC7870E225E0F208E233F208E233F208E233F2EA2AFAEB33FBEE33FE6F
:205EA900EF33FFEC6003430201786CC3E2F5F0EA95F008E2F5F0EB95F008E2F5F0EE95F0C5
:205EC90008E2F5F0EF95F04022786CD3E29AF4B3FAB308E29BF4B3FBB308E29EF4B3FEB3D3
:1D5EE90008E29FF4FF7870E24401F2DD917870E2F58208E2F58308E2F5F008E22233
:0600B200E478FFF6D8FD22
:200090007924E94400601B7A0190767178627593FCE493F2A308B800020593D9F4DAF275C7
:0200B00093FFBC
:1B5F060020F71130F6138883A88220F509F6A8837583002280FEF280F5F0229A
:205F2100AA83AB828BF090F075E0A4F8A9F08AF0E0A429F98BF0A3E0A429F58388822222D0
:205F4100AF82AE83ADF0FC787BEFF2EE08F2ED08F2EC08F2787BE2FF7879E28FF0A4FFAD46
:205F6100F07881EFF208EDF2787BE2FF7877E28FF0A4FFADF0787FEFF208EDF27882E2FF71
:205F8100787EE2FE7877E28EF0A42FFF7882F2787DE2FE7878E28EF0A42F7882F27881E25E
:205FA100FE08E2FF787DE2FD7877E28DF0A4ABF02EFEEB3FFF7881EEF208EFF2787CE2FDAE
:205FC1007878E28DF0A4ABF02EFEEB3FFF7881EEF208EFF2787CE2FF7879E28FF0A4787E5A
:205FE100F2787CE2FF7877E28FF0A4FFADF0787CEFF208EDF2787BE2FF787AE28FF0A4784A
:206001007AF2787BE2FF7878E28FF0A4FFADF07878EFF208EDF27877E4F2787BF2787FE273
:20602100F55A08E2F55B08E2F55C08E2F55D787BE2FA08E2FB08E2FE08E2FFEA255AF55A22
:20604100EB355BF55BEE355CF55CEF355DF55D787FE55AF208E55BF208E55CF208E55DF2F8
:20606100787FE208E208E208E27877E2FC08E2FD08E2FE08E2FFEC255AFCED355BFDEE35FF
:0C6081005CFEEF355D8C828D838EF0227A
:20608D00AE82AF837D007C007884E2235401FB70357883E225E0F208E233F27883C3E2F52A
:2060AD00F0EE95F008E2F5F0EF95F050147883E2F5F008E2C313C5F01318F2C5F008F28046
:2060CD00050C8C0580C27883C3E2F5F0EE95F008E2F5F0EF95F0400E7883D3E29EF4B3FE53
:2060ED00B308E29FF4FF7883E2F5F008E2C313C5F01318F2C5F008F28D041DEC70C88E827F
:03610D008F83225B
:20611000AD82AE83AFF07888E2F5F008E245F07004900000227888E2FB08E2FC1BBBFF01CB
:206130001CEB4C6069C003C0048D5A8E5B8F5C855A82855B83855CF012628FFA7885E2F58B
:206150005D08E2F55E08E2F55F855D82855E83855FF012628FFCEAB504028006D004D003E8
:20617000802C7401255AFAE4355BFBAC5C8A058B068C077401255DFAE4355EFBAC5F7885E0
:20619000EAF2EB08F2EC08F2D004D003808E8D828E838FF012628FFD7F007885E2FB08E2B1
:1A61B000FC08E2FE8B828C838EF012628FFB7E00EDC39BF582EF9EF58322F2
:2061CA00AE82E583FF33E433FD6009C3E49EFBE49FFC80048E038F04788BE233E433FF6079
:2061EA0010788AE2D3F43400FA08E2F43400FE8007788AE2FA08E2FE7862EAF2EE08F28B26
:20620A00828C83C007C005125D4AAC82AE83D005D007EF6D600AC3E49CF582E49EF58322F7
:05622A008C828E83222E
:2000B800788CE84400600A79007593F0E4F309D8FC78D6E8440B600C790C90F08CE4F0A370
:0400D800D8FCD9FA7D
:0F008100E4737581641262ABE582600302007E56
:20622F00758911758800758B00758D0020B0FD20B0FA20B0F720B0F420B0F130B0FDD28E21
:20624F0020B0FD30B0FD20B0FD30B0FDC28EE58D25E0FFE58B2354014FF58DE58D25E0FFE6
:20626F00E58B232354014FF58DE58DF4F58DE58D04F58D858D8B758921758780759852229A
:1C628F0020F71430F6148883A88220F507E6A88375830022E280F7E49322E0221E
:0462AB0075820022D6
:00000001FF

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -2,18 +2,33 @@
{
"00": {
"type": "SOL_M2_154_SSD",
"version": "0027",
"md5": "0cf7b60327ff2b0318185be2e8ba82a0"
"version": "0029",
"md5": "dafaaffbeefa29f16e9941bb531868d2"
},
"01": {
"type": "SOL_M2_29_SSD",
"version": "0027",
"md5": "a051ee5d5517b3e8d3d09ae1e076eabc"
"version": "0029",
"md5": "6f5eae3a7d96eb8e15a3fa15353ec75b"
},
"02": {
"type": "SOL_M2_42_SSD",
"version": "0027",
"md5": "94aa727296a4c480a6702ea612288b4c"
"version": "0029",
"md5": "fa8ac165ac0a9a1855ffdc2fa7c66528"
},
"12": {
"type": "SOL_M2_42_UC",
"version": "0029",
"md5": "b2b50c89add8f94c2b445c387609e212"
},
"03": {
"type": "SOL_M2_22_SSD",
"version": "0029",
"md5": "14662c9bc79d36316e5becd201537f59"
},
"04": {
"type": "SOL_M2_26_SSD",
"version": "0029",
"md5": "5caafca1428359b9516747f9a6c7fb45"
},
"05": {
"type": "SOL_M2_75_ota",
@@ -22,8 +37,8 @@
},
"11": {
"type": "SOL_M2_29_UC",
"version": "0027",
"md5": "aba3e35ef2bb63066d47e924bccd64f0"
"version": "0029",
"md5": "804c8613d87a3917237ce138e77e1ba4"
},
"21": {
"type": "SOL_M2_29_LT",
@@ -107,21 +122,21 @@
},
"80": {
"type": "chroma74y_ota",
"version": "0007",
"md5": "00616787b718375159a7ba709546bada"
"version": "0010",
"md5": "4baff1e9eba6949c26ea11dbbd105039"
},
"82": {
"type": "chroma29_ota",
"version": "0007",
"md5": "e583ec6bd715282dfbd4d9af0fb8c77c"
"version": "0010",
"md5": "0d4af3e5285ca20ac56926e6fdd71dbc"
},
"83": {
"type": "chroma42_ota",
"version": "0007",
"md5": "ca1c7736bc327a779fe3269329db2a02",
"version": "0010",
"md5": "bf4b7dad8acfab2f1504c4b57b8806db",
"type_1": "chroma42_8176_ota",
"version_1": "0007",
"md5_1": "170a59e493fad0b630588186cee47274"
"version_1": "0011",
"md5_1": "aeb07164835e9d16485c63d9de61b603"
}
}
]

View File

@@ -7,12 +7,6 @@
#define SOLUM_42_SSD1619 0x02
#define SOLUM_SEG_UK 0xF0
#define SOLUM_SEG_EU 0xF1
#define RESERVED_TESTING 0xFE
#define SOLUM_NODISPLAY 0xFF
#define ESP32_C6 0xC6
// overflow
#define SOLUM_M2_BWR_16 0x20
@@ -24,8 +18,6 @@
#define SOLUM_M2_BW_75 0x26
#define SOLUM_M2_BW_29 0x27
#define SOLUM_M3_BWR_97 0x2E
#define SOLUM_M3_BWR_43 0x2F
@@ -50,7 +42,6 @@
#define SOLUM_M3_BWR_116 0x37
#define SOLUM_M3_BWY_116 0x3F
// M3 Tags overflow
#define SOLUM_M3_BW_29 0x40
#define SOLUM_M3_BWR_58 0x41
@@ -80,7 +71,6 @@
#define GICI_BLE_TFT_21_BW 0xBA
#define GICI_BLE_EPD_BWR_29_SILABS 0xBD
#define GICI_BLE_UNKNOWN 0xBF
#define ATC_MI_THERMOMETER 0xBE
// Solum types - customer data byte 16 in M3 (nRF) UICR
#define STYPE_SIZE_016 0x40
@@ -98,6 +88,16 @@
#define STYPE_SIZE_097 0x64
#define STYPE_SIZE_013 0x4D
// Various types
#define ATC_MI_THERMOMETER 0xBE
#define RESERVED_TESTING 0xFE
#define SOLUM_NODISPLAY 0xFF
#define ESP32_C6 0xC6
#define BWRY_29 0xC0
#define ACEP_40 0xC1
#define SPECTRA_73 0xC2
@@ -110,6 +110,7 @@
#define CAPABILITY_HAS_WAKE_BUTTON 0x20
#define CAPABILITY_HAS_NFC 0x40
#define CAPABILITY_NFC_WAKE 0x80
#define CAPABILITY_IS_BLE 0x0100
#define DATATYPE_NOUPDATE 0
#define DATATYPE_IMG_BMP 2 // ** deprecated
@@ -117,10 +118,14 @@
#define DATATYPE_IMG_DIFF 0x10 // always 1BPP ** deprecated
#define DATATYPE_IMG_RAW_1BPP 0x20 // 2888 bytes for 1.54" / 4736 2.9" / 15000 4.2"
#define DATATYPE_IMG_RAW_2BPP 0x21 // 5776 bytes for 1.54" / 9472 2.9" / 30000 4.2"
#define DATATYPE_IMG_RAW_3BPP 0x22 // ACEP
#define DATATYPE_IMG_RAW_4BPP 0x23 // Spectra
#define DATATYPE_IMG_ZLIB 0x30 // compressed format.
// [uint32_t uncompressed size][2 byte zlib header][zlib compressed image]
// image format: [uint8_t header length][uint16_t width][uint16_t height][uint8_t bpp (lower 4)][img data]
#define DATATYPE_IMG_G5 0x31 // G5 compressed 1BPP
#define DATATYPE_UK_SEGMENTED 0x51 // Segmented data for the UK Segmented display type (contained in availableData Reply)
#define DATATYPE_EU_SEGMENTED 0x52 // Segmented data for the EU/DE Segmented display type (contained in availableData Reply)
#define DATATYPE_NFC_RAW_CONTENT 0xA0 // raw memory content for the NT3H1101

View File

@@ -1,5 +1,5 @@
{
"version": 2,
"version": 4,
"name": "M2 1.54\"",
"width": 152,
"height": 152,
@@ -10,9 +10,10 @@
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 2,
"options": [ "button", "customlut" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 10, 14, 15, 17, 18, 19, 20, 21, 27 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 10, 14, 17, 18, 19, 20, 21, 27 ],
"template": {
"1": {
"weekday": [ 76, 9, "fonts/calibrib30" ],

View File

@@ -1,5 +1,5 @@
{
"version": 2,
"version": 4,
"name": "M2 2.9\"",
"width": 296,
"height": 128,
@@ -10,9 +10,10 @@
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 2,
"options": [ "button", "customlut" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21, 27 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 27 ],
"template": {
"1": {
"weekday": [ 148, -3, "Signika-SB.ttf", 60 ],

View File

@@ -1,5 +1,5 @@
{
"version": 4,
"version": 5,
"name": "M2 4.2\"",
"width": 400,
"height": 300,
@@ -10,6 +10,7 @@
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 1,
"options": [ "button" ],
"contentids": [ 22, 23, 1, 4, 5, 7, 8, 9, 10, 11, 17, 18, 19, 20, 27 ],

View File

@@ -1,5 +1,5 @@
{
"version": 3,
"version": 6,
"name": "M2 2.2\"",
"width": 212,
"height": 104,
@@ -10,45 +10,45 @@
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 2,
"options": [ "button", "customlut" ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 27 ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 16, 17, 19, 20, 21, 22, 23, 27 ],
"template": {
"1": {
"weekday": [ 106, 2, "Signika-SB.ttf", 40 ],
"date": [ 106, 60, "Signika-SB.ttf", 30 ]
"weekday": [ 106, 5, "fonts/calibrib40", 40 ],
"date": [ 106, 55, "fonts/calibrib30", 30 ]
},
"2": {
"fonts": [ "Signika-SB.ttf", 150, 150, 150, 115, 90, 70 ],
"xy": [ 106, 52 ]
"fonts": [ "Signika-SB.ttf", 135, 135, 100, 90, 70, 50 ],
"xy": [ 106, 44 ]
},
"16": {
"location": [ 7, 15, "t0_14b_tf" ],
"title": [ 210, 11, "glasstown_nbp_tf" ],
"cols": [ 4, 121, 10, "glasstown_nbp_tf" ],
"bars": [ 10, 110, 8 ]
"location": [ 4, 10, "glasstown_nbp_tf" ],
"title": [ 165, 10, "glasstown_nbp_tf" ],
"cols": [ 4, 101, 8, "glasstown_nbp_tf" ],
"bars": [ 5, 90, 6 ]
},
"4": {
"location": [ 5, 3, "fonts/bahnschrift30" ],
"wind": [ 245, 3, "fonts/bahnschrift30" ],
"temp": [ 10, 60, "fonts/bahnschrift70" ],
"icon": [ 240, 20, 70, 2 ],
"dir": [ 210, -12, 40 ],
"location": [ 5, 3, "fonts/calibrib16.vlw" ],
"wind": [ 190, 5, "fonts/bahnschrift30" ],
"temp": [ 5, 30, "fonts/calibrib70" ],
"icon": [ 190, 25, 50, 2 ],
"dir": [ 165, -12, 40 ],
"umbrella": [ 175, -50, 25 ]
},
"8": {
"location": [ 5, 12, "t0_14b_tf" ],
"column": [ 5, 51 ],
"day": [ 28, 18, "fonts/twcondensed20", 41, 108 ],
"icon": [ 28, 55, 30 ],
"wind": [ 18, 26 ],
"line": [ 20, 128 ]
"column": [ 4, 53 ],
"day": [ 28, 18, "fonts/twcondensed20", 38, 88 ],
"icon": [ 28, 48, 25 ],
"wind": [ 18, 24 ],
"line": [ 20, 100 ]
},
"9": {
"title": [ 8, 2, "bahnschrift20.vlw", 25 ],
"items": 5,
"line": [ 8, 25, "REFSAN12.vlw" ],
"title": [ 3, 2, "fonts/calibrib16", 25 ],
"items": 4,
"line": [ 3, 20, "tahoma9.vlw" ],
"desc": [ 0, 5, "", 1 ]
},
"10": {
@@ -58,26 +58,26 @@
"11": {
"mode": 0,
"days": 1,
"title": [ 10, 2, "fonts/bahnschrift20" ],
"date": [ 245, 2 ],
"items": 7,
"red": [ 0, 21, 256, 14 ],
"line": [ 10, 32, 15, "t0_14b_tf", 50 ]
"title": [ 5, 2, "fonts/calibrib16.vlw" ],
"date": [ 205, 2 ],
"items": 8,
"red": [ 0, 21, 211, 10 ],
"line": [ 5, 21, 11, "tahoma9.vlw", 50 ]
},
"21": [
{ "text": [ 10, 5, "OpenEpaperLink AP", "bahnschrift20", 1, 0, 0 ] },
{ "text": [ 10, 50, "IP address:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 110, 50, "{ap_ip}", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 100, 50, "{ap_ip}", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 10, 70, "Channel:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 110, 70, "{ap_ch}", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 100, 70, "{ap_ch}", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 10, 90, "Tag count:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 110, 90, "{ap_tagcount}", "t0_14b_tf", 1, 0, 0 ] }
{ "text": [ 100, 90, "{ap_tagcount}", "t0_14b_tf", 1, 0, 0 ] }
],
"27": {
"bars": [ 26, 216, 80, 20 ],
"bars": [ 20, 180, 65, 12, 10 ],
"time": [ "tahoma9.vlw" ],
"yaxis": [ "tahoma9.vlw", 6, 6 ],
"head": [ "bahnschrift20.vlw" ]
"yaxis": [ "tahoma9.vlw", 2, 6 ],
"head": [ "fonts/calibrib16.vlw" ]
}
}
}

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 4,
"name": "M2 2.6\"",
"width": 296,
"height": 152,
@@ -10,44 +10,46 @@
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 2,
"options": [ "button", "customlut" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21, 27 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 27 ],
"template": {
"1": {
"weekday": [ 148, -3, "Signika-SB.ttf", 60 ],
"date": [ 148, 65, "Signika-SB.ttf", 48 ]
"weekday": [ 148, 9, "Signika-SB.ttf", 60 ],
"date": [ 148, 73, "Signika-SB.ttf", 48 ]
},
"2": {
"fonts": [ "Signika-SB.ttf", 150, 150, 150, 120, 100, 80 ],
"xy": [ 148, 53 ]
"fonts": [ "Signika-SB.ttf", 170, 170, 170, 120, 100, 80 ],
"xy": [ 148, 62 ]
},
"16": {
"location": [ 5, 5, "fonts/bahnschrift30" ],
"title": [ 247, 11, "glasstown_nbp_tf" ],
"cols": [ 1, 125, 12, "glasstown_nbp_tf" ],
"bars": [ 5, 111, 10 ]
"cols": [ 1, 149, 12, "glasstown_nbp_tf" ],
"bars": [ 5, 135, 10 ]
},
"4": {
"location": [ 5, 5, "fonts/bahnschrift30" ],
"wind": [ 280, 5, "fonts/bahnschrift30" ],
"temp": [ 5, 65, "fonts/bahnschrift70" ],
"icon": [ 285, 20, 70, 2 ],
"temp": [ 5, 58, "fonts/calibrib90" ],
"icon": [ 280, 35, 80, 2 ],
"dir": [ 235, -12, 40 ],
"umbrella": [ 190, -50, 25 ]
"umbrella": [ 190, -30, 25 ]
},
"8": {
"location": [ 5, 12, "t0_14b_tf" ],
"column": [ 5, 59 ],
"day": [ 30, 18, "fonts/twcondensed20", 41, 108 ],
"day": [ 30, 20, "fonts/twcondensed20", 41, 105 ],
"rain": [ 34, 130 ],
"icon": [ 30, 55, 30 ],
"wind": [ 18, 26 ],
"line": [ 20, 128 ]
"line": [ 20, 150 ]
},
"9": {
"title": [ 2, 0, "bahnschrift20.vlw", 25 ],
"title": [ 3, 2, "bahnschrift20.vlw", 25 ],
"items": 8,
"line": [ 1, 25, "REFSAN12.vlw" ],
"line": [ 3, 25, "REFSAN12.vlw" ],
"desc": [ 0, 5, "", 1 ]
},
"10": {
@@ -59,7 +61,7 @@
"days": 1,
"title": [ 5, 2, "fonts/bahnschrift20" ],
"date": [ 290, 2 ],
"items": 7,
"items": 9,
"red": [ 0, 21, 296, 14 ],
"line": [ 5, 32, 15, "t0_14b_tf", 50 ]
},
@@ -73,7 +75,7 @@
{ "text": [ 120, 90, "{ap_tagcount}", "t0_14b_tf", 1, 0, 0 ] }
],
"27": {
"bars": [ 9, 288, 90, 11 ],
"bars": [ 9, 288, 90, 11, 15 ],
"time": [ "tahoma9.vlw" ],
"yaxis": [ "tahoma9.vlw", 0, 6 ],
"head": [ "calibrib30.vlw" ]

View File

@@ -1,5 +1,5 @@
{
"version": 3,
"version": 4,
"name": "M2 7.4\"",
"width": 640,
"height": 384,
@@ -54,7 +54,7 @@
"gridparam": [ 3, 17, 30, "calibrib16.vlw", "tahoma9.vlw", 14 ]
},
"27": {
"bars": [ 18, 612, 350, 20 ],
"bars": [ 18, 612, 330, 20, 22 ],
"time": [ "calibrib16.vlw" ],
"yaxis": [ "calibrib16.vlw", 1, 12 ],
"head": [ "calibrib30.vlw" ]

View File

@@ -0,0 +1,19 @@
{
"version": 3,
"name": "Opticon 2.2\"",
"width": 250,
"height": 128,
"rotatebuffer": 3,
"bpp": 2,
"colortable": {
"white": [ 255, 255, 255 ],
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ],
"yellow": [ 255, 255, 0 ]
},
"g5_compression": "29",
"shortlut": 0,
"options": [ "led" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 14, 16, 17, 18, 19, 20, 21, 27 ],
"usetemplate": 177
}

View File

@@ -0,0 +1,19 @@
{
"version": 3,
"name": "Opticon 2.9\"",
"width": 296,
"height": 128,
"rotatebuffer": 3,
"bpp": 2,
"colortable": {
"white": [ 255, 255, 255 ],
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ],
"yellow": [ 255, 255, 0 ]
},
"g5_compression": "29",
"shortlut": 2,
"options": [ "led" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 14, 16, 17, 18, 19, 20, 21, 27 ],
"usetemplate": 1
}

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"name": "Opticon 4.2\"",
"width": 400,
"height": 300,
"rotatebuffer": 2,
"bpp": 2,
"colortable": {
"white": [ 255, 255, 255 ],
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 1,
"options": [ ],
"contentids": [ 22, 23, 1, 4, 5, 7, 8, 9, 10, 11, 17, 18, 19, 20, 27 ],
"usetemplate": 2
}

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"name": "Opticon 7.5\"",
"width": 640,
"height": 384,
"rotatebuffer": 2,
"bpp": 2,
"colortable": {
"white": [ 255, 255, 255 ],
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0]
},
"g5_compression": "29",
"shortlut": 1,
"options": [ ],
"contentids": [ 22, 23, 1, 4, 5, 7, 8, 9, 10, 11, 17, 18, 19, 20, 27 ],
"usetemplate" : 5
}

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 3,
"name": "M2 2.9\" (UC8151)",
"width": 296,
"height": 128,
@@ -10,8 +10,9 @@
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 0,
"options": [ "button" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18, 19, 20, 21, 27 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 14, 16, 17, 18, 19, 20, 21, 27 ],
"usetemplate": 1
}

View File

@@ -1,5 +1,5 @@
{
"version": 2,
"version": 3,
"name": "M2 4.2\" UC",
"width": 400,
"height": 300,
@@ -10,6 +10,7 @@
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 1,
"options": [ "button" ],
"contentids": [ 22, 23, 1, 4, 5, 7, 8, 9, 10, 11, 17, 18, 19, 20, 27 ],

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 2,
"name": "STGM29XXF 2.9\"",
"width": 296,
"height": 128,
@@ -12,6 +12,6 @@
"highlight_color": 5,
"shortlut": 0,
"options": [ "button", "customlut" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21 ],
"usetemplate": 1
}

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 2,
"name": "M2 2.7\"",
"width": 264,
"height": 176,
@@ -10,6 +10,7 @@
"black": [ 0, 0, 0 ],
"red": [ 255, 0, 0 ]
},
"g5_compression": "29",
"shortlut": 0,
"options": [ "button" ],
"contentids": [ 0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 17, 18, 19, 20 ],

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 3,
"name": "STGM29MT1 2.9\"",
"width": 296,
"height": 128,
@@ -9,9 +9,10 @@
"white": [ 255, 255, 255 ],
"black": [ 0, 0, 0 ]
},
"g5_compression": "29",
"highlight_color": 5,
"shortlut": 0,
"options": [ "button", "customlut" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21 ],
"usetemplate": 1
}

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 2,
"name": "M3 1.6\"",
"width": 200,
"height": 200,
@@ -14,7 +14,7 @@
"shortlut": 0,
"zlib_compression": "27",
"options": [ "button" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 10, 14, 15, 17, 18, 19, 20, 21 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 10, 14, 17, 18, 19, 20, 21 ],
"template": {
"1": {
"weekday": [ 100, 5, "fonts/calibrib40" ],

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 2,
"name": "M3 1.3\" Peghook",
"width": 144,
"height": 200,
@@ -14,7 +14,7 @@
"shortlut": 0,
"zlib_compression": "27",
"options": [ "button" ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 10, 14, 15, 17, 18, 19, 20, 21 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 10, 14, 17, 18, 19, 20, 21 ],
"template": {
"1": {
"weekday": [ 72, 12, "fonts/calibrib28" ],

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 2,
"name": "M3 2.2 Lite\"",
"width": 250,
"height": 128,
@@ -13,7 +13,7 @@
"shortlut": 0,
"zlib_compression": "27",
"options": [ "led" ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 27 ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 16, 17, 19, 20, 21, 22, 23, 27 ],
"template": {
"1": {

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 3,
"name": "HS BW 2.13\"",
"width": 256,
"height": 128,
@@ -10,8 +10,9 @@
"black": [ 0, 0, 0 ]
},
"shortlut": 0,
"zlib_compression": "27",
"options": [ "led" ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 27 ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 16, 17, 19, 20, 21, 22, 23, 27 ],
"template": {
"1": {

View File

@@ -1,5 +1,5 @@
{
"version": 2,
"version": 4,
"name": "HS BWR 2.13\"",
"width": 256,
"height": 128,
@@ -11,8 +11,9 @@
"red": [ 255, 0, 0 ]
},
"shortlut": 0,
"zlib_compression": "27",
"options": [ "led" ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 27 ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 16, 17, 19, 20, 21, 22, 23, 27 ],
"template": {
"1": {

View File

@@ -1,5 +1,5 @@
{
"version": 2,
"version": 5,
"name": "HS BWR 2.66\"",
"width": 296,
"height": 152,
@@ -11,73 +11,74 @@
"red": [ 255, 0, 0 ]
},
"shortlut": 0,
"zlib_compression": "27",
"options": [ "led" ],
"contentids": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 27 ],
"contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 27 ],
"template": {
"1": {
"weekday": [ 128, 3, "Signika-SB.ttf", 50 ],
"date": [ 128, 62, "Signika-SB.ttf", 40 ]
"weekday": [ 148, 9, "Signika-SB.ttf", 60 ],
"date": [ 148, 73, "Signika-SB.ttf", 48 ]
},
"2": {
"fonts": [ "Signika-SB.ttf", 150, 150, 150, 115, 90, 70 ],
"xy": [ 128, 53 ]
"fonts": [ "Signika-SB.ttf", 170, 170, 170, 120, 100, 80 ],
"xy": [ 148, 62 ]
},
"16": {
"location": [ 7, 15, "t0_14b_tf" ],
"title": [ 210, 11, "glasstown_nbp_tf" ],
"cols": [ 4, 121, 10, "glasstown_nbp_tf" ],
"bars": [ 10, 110, 8 ]
"location": [ 5, 5, "fonts/bahnschrift30" ],
"title": [ 247, 11, "glasstown_nbp_tf" ],
"cols": [ 1, 149, 12, "glasstown_nbp_tf" ],
"bars": [ 5, 135, 10 ]
},
"4": {
"location": [ 5, 3, "fonts/bahnschrift30" ],
"wind": [ 245, 3, "fonts/bahnschrift30" ],
"temp": [ 10, 60, "fonts/bahnschrift70" ],
"icon": [ 240, 20, 70, 2 ],
"dir": [ 210, -12, 40 ],
"umbrella": [ 175, -50, 25 ]
"location": [ 5, 5, "fonts/bahnschrift30" ],
"wind": [ 280, 5, "fonts/bahnschrift30" ],
"temp": [ 5, 58, "fonts/calibrib90" ],
"icon": [ 280, 35, 80, 2 ],
"dir": [ 235, -12, 40 ],
"umbrella": [ 190, -30, 25 ]
},
"8": {
"location": [ 5, 12, "t0_14b_tf" ],
"column": [ 5, 51 ],
"day": [ 28, 18, "fonts/twcondensed20", 41, 108 ],
"icon": [ 28, 55, 30 ],
"column": [ 5, 59 ],
"day": [ 30, 20, "fonts/twcondensed20", 41, 105 ],
"rain": [ 34, 130 ],
"icon": [ 30, 55, 30 ],
"wind": [ 18, 26 ],
"line": [ 20, 128 ]
"line": [ 20, 150 ]
},
"9": {
"title": [ 8, 2, "bahnschrift20.vlw", 25 ],
"items": 5,
"line": [ 8, 25, "REFSAN12.vlw" ],
"title": [ 3, 2, "bahnschrift20.vlw", 25 ],
"items": 8,
"line": [ 3, 25, "REFSAN12.vlw" ],
"desc": [ 0, 5, "", 1 ]
},
"10": {
"title": [ 128, 2, "calibrib16.vlw" ],
"pos": [ 128, 16 ]
"title": [ 149, 5, "fonts/bahnschrift20" ],
"pos": [ 149, 27 ]
},
"11": {
"mode": 0,
"days": 1,
"title": [ 10, 2, "fonts/bahnschrift20" ],
"date": [ 245, 2 ],
"items": 7,
"red": [ 0, 21, 256, 14 ],
"line": [ 10, 32, 15, "t0_14b_tf", 50 ]
"title": [ 5, 2, "fonts/bahnschrift20" ],
"date": [ 290, 2 ],
"items": 9,
"red": [ 0, 21, 296, 14 ],
"line": [ 5, 32, 15, "t0_14b_tf", 50 ]
},
"21": [
{ "text": [ 10, 5, "OpenEpaperLink AP", "bahnschrift20", 1, 0, 0 ] },
{ "text": [ 10, 50, "IP address:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 110, 50, "{ap_ip}", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 10, 70, "Channel:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 110, 70, "{ap_ch}", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 10, 90, "Tag count:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 110, 90, "{ap_tagcount}", "t0_14b_tf", 1, 0, 0 ] }
{ "text": [ 5, 5, "OpenEpaperLink AP", "bahnschrift20", 1, 0, 0 ] },
{ "text": [ 5, 50, "IP address:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 120, 50, "{ap_ip}", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 5, 70, "Channel:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 120, 70, "{ap_ch}", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 5, 90, "Tag count:", "t0_14b_tf", 1, 0, 0 ] },
{ "text": [ 120, 90, "{ap_tagcount}", "t0_14b_tf", 1, 0, 0 ] }
],
"27": {
"bars": [ 26, 216, 80, 20 ],
"bars": [ 9, 288, 90, 11, 15 ],
"time": [ "tahoma9.vlw" ],
"yaxis": [ "tahoma9.vlw", 6, 6 ],
"head": [ "bahnschrift20.vlw" ]
"yaxis": [ "tahoma9.vlw", 0, 6 ],
"head": [ "calibrib30.vlw" ]
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More