├── .clang-format ├── DALIDriver.cpp ├── DALIDriver.h ├── README.md └── manchester ├── encoder.cpp └── encoder.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: false 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | BreakAfterJavaFieldAnnotations: false 40 | BreakStringLiterals: true 41 | ColumnLimit: 80 42 | CommentPragmas: '^ IWYU pragma:' 43 | BreakBeforeInheritanceComma: false 44 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 45 | ConstructorInitializerIndentWidth: 4 46 | ContinuationIndentWidth: 4 47 | Cpp11BracedListStyle: true 48 | DerivePointerAlignment: false 49 | DisableFormat: false 50 | ExperimentalAutoDetectBinPacking: false 51 | FixNamespaceComments: true 52 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 53 | IncludeCategories: 54 | - Regex: '^"' 55 | Priority: 2 56 | - Regex: '^<' 57 | Priority: 3 58 | - Regex: '.*' 59 | Priority: 1 60 | IncludeIsMainRegex: '$' 61 | IndentCaseLabels: true 62 | IndentWidth: 4 63 | IndentWrappedFunctionNames: false 64 | JavaScriptQuotes: Leave 65 | JavaScriptWrapImports: true 66 | KeepEmptyLinesAtTheStartOfBlocks: true 67 | MacroBlockBegin: '' 68 | MacroBlockEnd: '' 69 | MaxEmptyLinesToKeep: 1 70 | NamespaceIndentation: None 71 | PenaltyBreakBeforeFirstCallParameter: 19 72 | PenaltyBreakComment: 300 73 | PenaltyBreakFirstLessLess: 120 74 | PenaltyBreakString: 1000 75 | PenaltyExcessCharacter: 1000000 76 | PenaltyReturnTypeOnItsOwnLine: 60 77 | PointerAlignment: Right 78 | ReflowComments: true 79 | SortIncludes: true 80 | SpaceAfterCStyleCast: false 81 | SpaceAfterTemplateKeyword: true 82 | SpaceBeforeAssignmentOperators: true 83 | SpaceBeforeParens: ControlStatements 84 | SpaceInEmptyParentheses: false 85 | SpacesBeforeTrailingComments: 1 86 | SpacesInAngles: false 87 | SpacesInContainerLiterals: true 88 | SpacesInCStyleCastParentheses: false 89 | SpacesInParentheses: false 90 | SpacesInSquareBrackets: false 91 | Standard: Cpp03 92 | TabWidth: 4 93 | UseTab: Never 94 | ... 95 | 96 | -------------------------------------------------------------------------------- /DALIDriver.cpp: -------------------------------------------------------------------------------- 1 | /* DALI Driver 2 | * Copyright (c) 2018 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "DALIDriver.h" 18 | 19 | DALIDriver::DALIDriver(PinName out_pin, PinName in_pin, int baud, 20 | bool idle_state) 21 | : encoder(out_pin, in_pin, baud, idle_state) 22 | { 23 | } 24 | 25 | DALIDriver::~DALIDriver() 26 | { 27 | } 28 | 29 | bool DALIDriver::add_to_group(uint8_t addr, uint8_t group) 30 | { 31 | // Send the command to add to group 32 | send_twice(addr, ADD_TO_GROUP + group); 33 | // Query upper or lower bits of gearGroups 16 bit variable 34 | uint8_t cmd = group < 8 ? QUERY_GEAR_GROUPS_L : QUERY_GEAR_GROUPS_H; 35 | // Send query command 36 | send_command_standard(addr, cmd); 37 | // Receive gearGroups variable 38 | uint8_t resp = encoder.recv(); 39 | // Group bit will be set if this light is a memeber of that group 40 | uint8_t mask = 1 << (group % 8); 41 | bool contained = resp & mask; 42 | // Return whether light is part of group 43 | return contained; 44 | } 45 | 46 | bool DALIDriver::remove_from_group(uint8_t addr, uint8_t group) 47 | { 48 | // Send the command to remove from group 49 | send_twice(addr, REMOVE_FROM_GROUP + group); 50 | // Query upper or lower bits of gearGroups 16 bit variable 51 | uint8_t cmd = group < 8 ? QUERY_GEAR_GROUPS_L : QUERY_GEAR_GROUPS_H; 52 | // Send query command 53 | send_command_standard(addr, cmd); 54 | // Receive gearGroups variable 55 | uint8_t resp = encoder.recv(); 56 | // Group bit will be set if this light is a memeber of that group 57 | uint8_t mask = 1 << (group % 8); 58 | bool contained = resp & mask; 59 | // Return whether light is not part of group 60 | return !contained; 61 | } 62 | 63 | void DALIDriver::set_level(uint8_t addr, uint8_t level) 64 | { 65 | send_command_direct(addr, level); 66 | } 67 | 68 | void DALIDriver::turn_off(uint8_t addr) 69 | { 70 | send_command_standard(addr, OFF); 71 | } 72 | 73 | uint8_t DALIDriver::get_level(uint8_t addr) 74 | { 75 | send_command_standard(addr, QUERY_ACTUAL_LEVEL); 76 | uint8_t resp = encoder.recv(); 77 | return resp; 78 | } 79 | 80 | uint8_t DALIDriver::get_error(uint8_t addr) 81 | { 82 | send_command_standard(addr, QUERY_ERROR); 83 | uint8_t resp = encoder.recv(); 84 | return resp & 0x03; 85 | } 86 | 87 | uint8_t DALIDriver::get_phm(uint8_t addr) 88 | { 89 | send_command_standard(addr, QUERY_PHM); 90 | uint8_t resp = encoder.recv(); 91 | return resp; 92 | } 93 | 94 | uint8_t DALIDriver::get_fade(uint8_t addr) 95 | { 96 | send_command_standard(addr, QUERY_FADE); 97 | uint8_t resp = encoder.recv(); 98 | return resp; 99 | } 100 | 101 | ColorType DALIDriver::get_color_type(uint8_t addr) { 102 | uint8_t channels = query_rgbwaf_channels(addr); 103 | if (channels == 4) { 104 | return RGB; 105 | } else if (query_temperature_capable(addr)) { 106 | return TEMPERATURE; 107 | } 108 | return UNSUPPORTED; 109 | } 110 | 111 | uint8_t DALIDriver::query_color_type_features(uint8_t addr) 112 | { 113 | encoder.set_recv_frame_length(8); 114 | //send command to enable device type 8 115 | send_command_special(ENABLE_DEVICE_TYPE, 0x08); 116 | send_command_standard(addr, QUERY_COLOR_TYPE_FEATURES); 117 | uint8_t resp = encoder.recv(); 118 | return resp; 119 | } 120 | 121 | uint8_t DALIDriver::query_rgbwaf_channels(uint8_t addr) 122 | { 123 | uint8_t resp = query_color_type_features(addr); 124 | return (resp & 0xE0) >> 5; 125 | } 126 | 127 | bool DALIDriver::query_temperature_capable(uint8_t addr) 128 | { 129 | uint8_t resp = query_color_type_features(addr); 130 | return (resp & 0x02) >> 1; 131 | } 132 | 133 | 134 | void DALIDriver::set_color_temp(uint8_t addr, uint16_t temp) 135 | { 136 | // Calculate Mirek from Kelvin 137 | temp = 1000000/temp; 138 | // Set Temp 139 | send_command_special(DTR0, temp & 0x00FF); 140 | send_command_special(DTR1, temp >> 8); 141 | //send command to enable device type 8 142 | send_command_special(ENABLE_DEVICE_TYPE, 0x08); 143 | // Set the temporary color to the temperature 144 | send_command_standard(addr, SET_TEMP_TEMPC); 145 | } 146 | 147 | void DALIDriver::set_color_scene(uint8_t addr, uint8_t scene, uint16_t temp) 148 | { 149 | set_color_temp(addr, temp); 150 | // Get the current scene level 151 | send_command_standard(addr, QUERY_SCENE_LEVEL + scene); 152 | uint8_t scene_level = encoder.recv(); 153 | send_command_special(DTR0, scene_level); 154 | 155 | // Store what is in the temperorary color as scene color and also scene level to DTR0 156 | send_twice(addr, STORE_DTR_AS_SCENE + scene); 157 | } 158 | 159 | void DALIDriver::set_color(uint8_t addr, uint16_t temp) 160 | { 161 | set_color_temp(addr, temp); 162 | // Activate color 163 | //send command to enable device type 8 164 | send_command_special(ENABLE_DEVICE_TYPE, 0x08); 165 | send_command_standard(addr, COLOR_ACTIVATE); 166 | } 167 | 168 | void DALIDriver::set_color_temp(uint8_t addr, uint8_t r, uint8_t g, uint8_t b, uint8_t dim) 169 | { 170 | // Set RGB 171 | send_command_special(DTR0, r); 172 | send_command_special(DTR1, g); 173 | send_command_special(DTR2, b); 174 | //send command to enable device type 8 175 | send_command_special(ENABLE_DEVICE_TYPE, 0x08); 176 | send_command_standard(addr, SET_TEMP_RGB_DIM); 177 | 178 | // Set dim 179 | send_command_special(DTR0, dim); 180 | //send command to enable device type 8 181 | send_command_special(ENABLE_DEVICE_TYPE, 0x08); 182 | send_command_standard(addr, SET_TEMP_WAF_DIM); 183 | } 184 | 185 | void DALIDriver::set_color_scene(uint8_t addr, uint8_t scene, uint8_t r, uint8_t g, uint8_t b, uint8_t dim) 186 | { 187 | set_color_temp(addr, r, g, b, dim); 188 | // Get the current scene level 189 | send_command_standard(addr, QUERY_SCENE_LEVEL + scene); 190 | uint8_t scene_level = encoder.recv(); 191 | send_command_special(DTR0, scene_level); 192 | 193 | // Store what is in the temperorary color as scene color and also scene level to DTR0 194 | send_twice(addr, STORE_DTR_AS_SCENE + scene); 195 | } 196 | 197 | void DALIDriver::set_color(uint8_t addr, uint8_t r, uint8_t g, uint8_t b, uint8_t dim) 198 | { 199 | set_color_temp(addr, r, g, b, dim); 200 | // Activate color 201 | //send command to enable device type 8 202 | send_command_special(ENABLE_DEVICE_TYPE, 0x08); 203 | send_command_standard(addr, COLOR_ACTIVATE); 204 | } 205 | 206 | 207 | uint32_t DALIDriver::recv() 208 | { 209 | return encoder.recv(); 210 | } 211 | 212 | uint32_t DALIDriver::query_instances(uint8_t addr) 213 | { 214 | encoder.set_recv_frame_length(8); 215 | send_command_standard_input(addr, 0xFE, 0x35); 216 | uint32_t resp = encoder.recv(); 217 | return resp; 218 | } 219 | 220 | void DALIDriver::turn_on(uint8_t addr) 221 | { 222 | send_command_standard(addr, ON_AND_STEP_UP); 223 | } 224 | 225 | void DALIDriver::send_twice(uint8_t addr, uint8_t opcode) 226 | { 227 | send_command_standard(addr, opcode); 228 | send_command_standard(addr, opcode); 229 | } 230 | 231 | void DALIDriver::set_fade_time(uint8_t addr, uint8_t time) 232 | { 233 | // Send twice command 234 | send_command_special(DTR0, time); 235 | send_twice(addr, SET_FADE_TIME); 236 | } 237 | 238 | void DALIDriver::set_fade_rate(uint8_t addr, uint8_t rate) 239 | { 240 | // Send twice command 241 | send_command_special(DTR0, rate); 242 | send_twice(addr, SET_FADE_RATE); 243 | } 244 | 245 | void DALIDriver::set_scene(uint8_t addr, uint8_t scene, uint8_t level) 246 | { 247 | send_command_special(DTR0, level); 248 | // Send twice command 249 | send_twice(addr, SET_SCENE + scene); 250 | } 251 | 252 | void DALIDriver::remove_from_scene(uint8_t addr, uint8_t scene) 253 | { 254 | send_twice(addr, REMOVE_FROM_SCENE + scene); 255 | } 256 | 257 | void DALIDriver::go_to_scene(uint8_t addr, uint8_t scene) 258 | { 259 | send_twice(addr, GO_TO_SCENE + scene); 260 | //send command to enable device type 8 261 | send_command_special(ENABLE_DEVICE_TYPE, 0x08); 262 | // Activate color scene 263 | send_command_standard(addr, COLOR_ACTIVATE); 264 | } 265 | 266 | event_msg DALIDriver::parse_event(uint32_t data) 267 | { 268 | event_msg msg; 269 | msg.addr = data >> 17; 270 | msg.inst_type = (data >> 10) & 0x7F; 271 | msg.info = data & 0x03FF; 272 | return msg; 273 | } 274 | 275 | void DALIDriver::attach(mbed::Callback status_cb) 276 | { 277 | quiet_mode(false); 278 | encoder.attach(status_cb); 279 | } 280 | 281 | void DALIDriver::detach() 282 | { 283 | quiet_mode(true); 284 | encoder.detach(); 285 | } 286 | 287 | void DALIDriver::reattach() 288 | { 289 | quiet_mode(false); 290 | encoder.reattach(); 291 | } 292 | 293 | void DALIDriver::send_command_special(uint8_t address, uint8_t opcode) 294 | { 295 | encoder.send(((uint16_t)address << 8) | opcode); 296 | } 297 | 298 | void DALIDriver::send_command_special_input(uint8_t instance, uint8_t opcode) 299 | { 300 | encoder.send_24(((uint32_t)0xC1 << 16) | ((uint16_t)instance << 8) | 301 | opcode); 302 | } 303 | 304 | void DALIDriver::send_command_standard_input(uint8_t address, uint8_t instance, 305 | uint8_t opcode) 306 | { 307 | // Get the upper bit 308 | uint8_t mask = address & 0x80; 309 | // Change address to have 1 in LSb to signify 'standard command' 310 | address = mask | ((address << 1) + 1); 311 | encoder.send_24(((uint32_t)address << 16) | ((uint16_t)instance << 8) | 312 | opcode); 313 | } 314 | 315 | void DALIDriver::send_command_standard(uint8_t address, uint8_t opcode) 316 | { 317 | // Get the upper bit 318 | uint8_t mask = address & 0x80; 319 | // Change address to have 1 in LSb to signify 'standard command' 320 | address = mask | ((address << 1) + 1); 321 | encoder.send(((uint16_t)address << 8) | opcode); 322 | } 323 | 324 | void DALIDriver::send_command_direct(uint8_t address, uint8_t opcode) 325 | { 326 | // Get the upper bit 327 | uint8_t mask = address & 0x80; 328 | // Change address to have 0 in LSb to signify 'direct arc power' 329 | address = mask | (address << 1); 330 | encoder.send(((uint16_t)address << 8) | opcode); 331 | } 332 | 333 | bool DALIDriver::check_response(uint8_t expected) 334 | { 335 | int response = encoder.recv(); 336 | if (response < 0) 337 | return false; 338 | return (response == expected); 339 | } 340 | 341 | int DALIDriver::getIndexOfLogicalUnit(uint8_t addr) 342 | { 343 | send_command_special(DTR1, 0x00); 344 | send_command_special(DTR0, 0x1A); 345 | send_command_special(READ_MEM_LOC, (addr << 1) + 1); 346 | return encoder.recv(); 347 | } 348 | 349 | void DALIDriver::set_search_address(uint32_t val) 350 | { 351 | send_command_special(SEARCHADDRH, val >> 16); 352 | send_command_special(SEARCHADDRM, (val >> 8) & (0x00FF)); 353 | send_command_special(SEARCHADDRL, val & 0x0000FF); 354 | } 355 | 356 | void DALIDriver::set_search_address_input(uint32_t val) 357 | { 358 | send_command_special_input(0x05, val >> 16); 359 | send_command_special_input(0x06, (val >> 8) & (0x00FF)); 360 | send_command_special_input(0x07, val & 0x0000FF); 361 | } 362 | 363 | uint8_t DALIDriver::get_group_addr(uint8_t group_number) 364 | { 365 | uint8_t mask = 1 << 7; 366 | // Make MSb a 1 to signify >1 device being addressed 367 | return mask | group_number; 368 | } 369 | 370 | void DALIDriver::quiet_mode(bool on) 371 | { 372 | if (on) { 373 | send_command_standard_input(0xFF, 0xFE, 0x1D); 374 | } else { 375 | send_command_standard_input(0xFF, 0xFE, 0x1E); 376 | } 377 | } 378 | 379 | float DALIDriver::get_temperature(uint8_t addr, uint8_t instance) 380 | { 381 | send_command_standard_input(addr, instance, 0x8C); 382 | int temp = encoder.recv(); 383 | send_command_standard_input(addr, instance, 0x8D); 384 | int temp2 = encoder.recv(); 385 | // Temperature, 10 bit, resolution 0.1C, -5C - 60C (value of 0 = -5C, 1 = 386 | // -4.9C, etc.) 387 | return ((float)((temp << 2) | (temp2 >> 6)) - 50.0f) * 0.1f; 388 | } 389 | 390 | float DALIDriver::get_humidity(uint8_t addr, uint8_t instance) 391 | { 392 | send_command_standard_input(addr, instance, 0x8C); 393 | int humidity = encoder.recv(); 394 | // Humidity, 8 bit, resolution 0.5%, 0-100% 395 | return ((float)humidity) / 2.0f; 396 | } 397 | 398 | int DALIDriver::init_lights() 399 | { 400 | quiet_mode(true); 401 | // TODO: does this need to happen every time controller boots? 402 | num_lights = assign_addresses(); 403 | return num_lights; 404 | } 405 | 406 | int DALIDriver::init_inputs() 407 | { 408 | quiet_mode(true); 409 | num_inputs = assign_addresses_input(true, num_lights) - num_lights; 410 | return num_inputs; 411 | } 412 | 413 | int DALIDriver::init() 414 | { 415 | num_logical_units = num_lights + num_inputs; 416 | init_lights(); 417 | init_inputs(); 418 | // Set the event scheme for all events to be address / instance id / event 419 | // info 420 | set_event_scheme(0xFF, 0xFF, 0x01); 421 | wait(1); 422 | for (int i = num_lights; i < num_inputs + num_lights; i++) { 423 | int inst = query_instances(i); 424 | for (int j = 0; j < inst; j++) { 425 | int inst_type = get_instance_type(i, j); 426 | if (inst_type == 4) { 427 | // Disable lumen 428 | disable_instance(i, j); 429 | continue; 430 | } 431 | enable_instance(i, j); 432 | // Filter events for PIR, only movement/no movement 433 | if (inst_type == 3) { 434 | set_event_filter(i, j, 0x1C); 435 | } 436 | } 437 | } 438 | return num_lights + num_inputs; 439 | } 440 | 441 | void DALIDriver::set_event_scheme(uint8_t addr, uint8_t inst, uint8_t scheme) 442 | { 443 | // Put scheme in DTR0 444 | send_command_special_input(0x30, scheme); 445 | // Set the event scheme 446 | send_command_standard_input(addr, inst, 0x67); 447 | send_command_standard_input(addr, inst, 0x67); 448 | } 449 | 450 | void DALIDriver::set_event_filter(uint8_t addr, uint8_t inst, uint8_t filter) 451 | { 452 | // Put filter in DTR0 453 | send_command_special_input(0x30, filter); 454 | // Set the event filter 455 | send_command_standard_input(addr, inst, 0x68); 456 | send_command_standard_input(addr, inst, 0x68); 457 | } 458 | 459 | uint8_t DALIDriver::get_instance_type(uint8_t addr, uint8_t inst) 460 | { 461 | send_command_standard_input(addr, inst, 0x80); 462 | return encoder.recv(); 463 | } 464 | uint8_t DALIDriver::get_instance_status(uint8_t addr, uint8_t inst) 465 | { 466 | send_command_standard_input(addr, inst, 0x86); 467 | return encoder.recv(); 468 | } 469 | 470 | void DALIDriver::disable_instance(uint8_t addr, uint8_t inst) 471 | { 472 | send_command_standard_input(addr, inst, 0x63); 473 | send_command_standard_input(addr, inst, 0x63); 474 | } 475 | 476 | void DALIDriver::enable_instance(uint8_t addr, uint8_t inst) 477 | { 478 | send_command_standard_input(addr, inst, 0x62); 479 | send_command_standard_input(addr, inst, 0x62); 480 | } 481 | 482 | int DALIDriver::get_highest_address() 483 | { 484 | int highestAssigned = -1; 485 | // Start initialization phase 486 | send_command_special(INITIALISE, 0x00); 487 | send_command_special(INITIALISE, 0x00); 488 | // Assign all units a random address 489 | send_command_special(RANDOMISE, 0x00); 490 | send_command_special(RANDOMISE, 0x00); 491 | wait_ms(100); 492 | 493 | while (true) { 494 | // Set the search address to the highest range 495 | set_search_address(0xFFFFFF); 496 | // Compare logical units search address to global search address 497 | send_command_special(COMPARE, 0x00); 498 | // Check if any device responds yes 499 | bool yes = check_response(YES); 500 | // If no devices are unassigned (all withdrawn), we are done 501 | if (!yes) { 502 | break; 503 | } 504 | uint32_t searchAddr = 0xFFFFFF; 505 | for (int i = 23; i >= 0; i--) { 506 | uint32_t mask = 1 << i; 507 | searchAddr = searchAddr & (~mask); 508 | // Set a new search address 509 | set_search_address(searchAddr); 510 | send_command_special(COMPARE, 0x00); 511 | // Check if any devices match 512 | bool yes = check_response(YES); 513 | if (!yes) { 514 | // No unit here, revert the mask 515 | searchAddr = searchAddr | mask; 516 | } 517 | // If yes, then we found at least one device 518 | } 519 | set_search_address(searchAddr); 520 | send_command_special(COMPARE, 0x00); 521 | yes = check_response(YES); 522 | if (yes) { 523 | // Get the current short address 524 | send_command_special(QUERY_SHORT_ADDR, 0x00); 525 | uint8_t short_addr = encoder.recv(); 526 | if (short_addr != 0xFF) { 527 | short_addr = short_addr >> 1; 528 | if (short_addr > highestAssigned) { 529 | highestAssigned = short_addr; 530 | } 531 | } 532 | // Tell unit to withdraw (no longer respond to search queries) 533 | send_command_special(WITHDRAW, 0x00); 534 | } 535 | // Refresh initialization state 536 | send_command_special(INITIALISE, 0x00); 537 | send_command_special(INITIALISE, 0x00); 538 | } 539 | 540 | send_command_special(TERMINATE, 0x00); 541 | return highestAssigned; 542 | } 543 | 544 | // Return number of logical units on the bus 545 | int DALIDriver::assign_addresses(bool reset) 546 | { 547 | uint8_t numAssignedShortAddresses = 0; 548 | int assignedAddresses[63] = {false}; 549 | int highestAssigned = -1; 550 | 551 | if (!reset) { 552 | // Get the highest address already assigned 553 | int highest_addr = get_highest_address(); 554 | if (highest_addr >= 0) { 555 | numAssignedShortAddresses = highest_addr + 1; 556 | for (int i = 0; i < numAssignedShortAddresses; i++) { 557 | assignedAddresses[i] = true; 558 | } 559 | } 560 | } 561 | // Start initialization phase for devices w/o a short address 562 | uint8_t opcode = reset ? 0x00 : 0xFF; 563 | send_command_special(INITIALISE, opcode); 564 | send_command_special(INITIALISE, opcode); 565 | // Assign all units a random address 566 | send_command_special(RANDOMISE, 0x00); 567 | send_command_special(RANDOMISE, 0x00); 568 | wait_ms(100); 569 | 570 | while (true) { 571 | // Set the search address to the highest range 572 | set_search_address(0xFFFFFF); 573 | // Compare logical units search address to global search address 574 | send_command_special(COMPARE, 0x00); 575 | // Check if any device responds yes 576 | bool yes = check_response(YES); 577 | // If no devices are unassigned (all withdrawn), we are done 578 | if (!yes) { 579 | break; 580 | } 581 | if (numAssignedShortAddresses < 63) { 582 | uint32_t searchAddr = 0xFFFFFF; 583 | for (int i = 23; i >= 0; i--) { 584 | uint32_t mask = 1 << i; 585 | searchAddr = searchAddr & (~mask); 586 | // Set a new search address 587 | set_search_address(searchAddr); 588 | send_command_special(COMPARE, 0x00); 589 | // Check if any devices match 590 | bool yes = check_response(YES); 591 | if (!yes) { 592 | // No unit here, revert the mask 593 | searchAddr = searchAddr | mask; 594 | } 595 | // If yes, then we found at least one device 596 | } 597 | set_search_address(searchAddr); 598 | send_command_special(COMPARE, 0x00); 599 | bool yes = check_response(YES); 600 | if (yes) { 601 | // We found a unit, let's program the short address with a new 602 | // address Give it a temporary short address 603 | uint8_t new_addr = numAssignedShortAddresses; 604 | if (new_addr < 63) { 605 | if (assignedAddresses[new_addr] == true) { 606 | // Duplicate addr? 607 | } else { 608 | // Program new address as short address 609 | send_command_special(PROGRAM_SHORT_ADDR, 610 | (new_addr << 1) + 1); 611 | // Tell unit to withdraw (no longer respond to search 612 | // queries) 613 | send_command_special(WITHDRAW, 0x00); 614 | numAssignedShortAddresses++; 615 | assignedAddresses[new_addr] = true; 616 | if (new_addr > highestAssigned) 617 | highestAssigned = new_addr; 618 | } 619 | } else { 620 | // expected < 63 ? 621 | } 622 | } else { 623 | // No device found 624 | } 625 | } 626 | // Refresh initialization state 627 | send_command_special(INITIALISE, 0x00); 628 | send_command_special(INITIALISE, 0x00); 629 | } 630 | 631 | send_command_special(TERMINATE, 0x00); 632 | return numAssignedShortAddresses; 633 | } 634 | 635 | // Return number of logical units on the bus 636 | int DALIDriver::assign_addresses_input(bool reset, int num_found) 637 | { 638 | send_command_special(TERMINATE, 0x00); 639 | uint8_t numAssignedShortAddresses = num_found; 640 | int assignedAddresses[63] = {false}; 641 | int highestAssigned = -1; 642 | 643 | // Put 0x00 in DTR0 644 | send_command_special_input(0x30, 0x00); 645 | // Set operating mode to DTR0 646 | send_command_standard_input(0xFF, 0xFE, 0x18); 647 | send_command_standard_input(0xFF, 0xFE, 0x18); 648 | 649 | // DTR0 MASK 650 | send_command_special_input(0x30, 0xFF); 651 | // Set short address to DTR0 652 | send_command_standard_input(0x7F, 0xFE, 0x14); 653 | send_command_standard_input(0x7F, 0xFE, 0x14); 654 | // Start initialization phase for devices 655 | send_command_special_input(0x01, 0xFF); 656 | send_command_special_input(0x01, 0xFF); 657 | // Assign all units a random address 658 | send_command_special_input(0x02, 0x00); 659 | send_command_special_input(0x02, 0x00); 660 | wait_ms(100); 661 | 662 | while (true) { 663 | // Set the search address to the highest range 664 | set_search_address_input(0xFFFFFF); 665 | // Compare logical units search address to global search address 666 | send_command_special_input(0x03, 0x00); 667 | // Check if any device responds yes 668 | bool yes = check_response(YES); 669 | // If no devices are unassigned (all withdrawn), we are done 670 | if (!yes) { 671 | break; 672 | } 673 | if (numAssignedShortAddresses < 63) { 674 | uint32_t searchAddr = 0xFFFFFF; 675 | for (int i = 23; i >= 0; i--) { 676 | uint32_t mask = 1 << i; 677 | searchAddr = searchAddr & (~mask); 678 | // Set a new search address 679 | set_search_address_input(searchAddr); 680 | send_command_special_input(0x03, 0x00); 681 | // Check if any devices match 682 | bool yes = check_response(YES); 683 | if (!yes) { 684 | // No unit here, revert the mask 685 | searchAddr = searchAddr | mask; 686 | } 687 | // If yes, then we found at least one device 688 | } 689 | set_search_address_input(searchAddr); 690 | send_command_special_input(0x03, 0x00); 691 | bool yes = check_response(YES); 692 | if (yes) { 693 | // We found a unit, let's program the short address with a new 694 | // address Give it a temporary short address 695 | uint8_t new_addr = numAssignedShortAddresses; 696 | if (new_addr < 63) { 697 | if (assignedAddresses[new_addr] == true) { 698 | // Duplicate addr? 699 | } else { 700 | // Program new address as short address 701 | send_command_special_input(0x08, new_addr); 702 | // Tell unit to withdraw (no longer respond to search 703 | // queries) 704 | send_command_special_input(0x04, 0x00); 705 | numAssignedShortAddresses++; 706 | assignedAddresses[new_addr] = true; 707 | if (new_addr > highestAssigned) 708 | highestAssigned = new_addr; 709 | } 710 | } else { 711 | // expected < 63 ? 712 | } 713 | } else { 714 | // No device found 715 | } 716 | } 717 | // Refresh initialization state 718 | send_command_special_input(0x01, 0x7F); 719 | send_command_special_input(0x01, 0x7F); 720 | } 721 | 722 | send_command_special_input(0x00, 0x00); 723 | return numAssignedShortAddresses; 724 | } 725 | -------------------------------------------------------------------------------- /DALIDriver.h: -------------------------------------------------------------------------------- 1 | /* DALI Driver 2 | * Copyright (c) 2018 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef DALI_DRIVER_H 18 | #define DALI_DRIVER_H 19 | 20 | #include "manchester/encoder.h" 21 | #include "mbed.h" 22 | 23 | // Special commands that do not address a specific device 24 | // These values will be used as address byte in DALI command 25 | enum SpecialCommandOpAddr { 26 | SEARCHADDRH = 0xB1, 27 | SEARCHADDRM = 0xB3, 28 | SEARCHADDRL = 0xB5, 29 | DTR0 = 0xA3, 30 | DTR1 = 0xC3, 31 | DTR2 = 0xC5, 32 | INITIALISE = 0xA5, 33 | RANDOMISE = 0xA7, 34 | PROGRAM_SHORT_ADDR = 0xB7, 35 | QUERY_SHORT_ADDR = 0xBB, 36 | COMPARE = 0xA9, 37 | TERMINATE = 0xA1, 38 | ENABLE_DEVICE_TYPE = 0xC1, 39 | WITHDRAW = 0xAB 40 | }; 41 | 42 | // Command op codes 43 | enum CommandOpCodes { 44 | GO_TO_SCENE = 0x10, 45 | OFF = 0x00, 46 | ON_AND_STEP_UP = 0x08, 47 | QUERY_GEAR_GROUPS_L = 0xC0, // get lower byte of gear groups status 48 | QUERY_GEAR_GROUPS_H = 0xC1, // get upper byte of gear groups status 49 | QUERY_ACTUAL_LEVEL = 0xA0, 50 | QUERY_ERROR = 0x90, 51 | QUERY_PHM = 0x9A, 52 | QUERY_FADE = 0xA5, 53 | QUERY_COLOR_TYPE_FEATURES = 0xF9, 54 | QUERY_SCENE_LEVEL = 0xB0, 55 | READ_MEM_LOC = 0xC5, 56 | SET_TEMP_RGB_DIM = 0xEB, 57 | SET_TEMP_TEMPC = 0xE7, 58 | SET_TEMP_WAF_DIM = 0xEC, 59 | COLOR_ACTIVATE = 0xE2, 60 | 61 | // Commands below are "send twice" 62 | SET_SCENE = 0x40, 63 | SET_FADE_TIME = 0x2E, 64 | SET_FADE_RATE = 0x2F, 65 | SET_MIN_LEVEL = 0x2B, 66 | REMOVE_FROM_SCENE = 0x50, 67 | REMOVE_FROM_GROUP = 0x70, 68 | STORE_DTR_AS_SCENE =0x40, 69 | ADD_TO_GROUP = 0x60, 70 | SET_SHORT_ADDR = 0x80, 71 | SET_MAX_LEVEL = 0x2A 72 | }; 73 | 74 | enum InstanceType { GENERIC = 0, OCCUPANCY = 3, LIGHT = 4, BUTTON = 1 }; 75 | enum ColorType { RGB, TEMPERATURE, UNSUPPORTED }; 76 | 77 | #define YES 0xFF 78 | 79 | class DALIDriver { 80 | public: 81 | /** Constructor DALIDriver 82 | * 83 | * @param out_pin Output pin for the DALI commands 84 | * @param in_pin Input pin for responses from DALI slaves 85 | * @param baud Signal baud rate 86 | * @param idle_state The default state of the line (high for DALI) 87 | */ 88 | DALIDriver(PinName out_pin, PinName in_pin, int baud = 1200, 89 | bool idle_state = 0); 90 | 91 | ~DALIDriver(); 92 | 93 | /** Initialise the driver 94 | * 95 | * @returns the number of logical units on the bus 96 | * 97 | */ 98 | int init(); 99 | 100 | /** Initialise the luminaires on the bus (give them addresses) 101 | * 102 | * @returns the number of luminaires on the bus 103 | * 104 | */ 105 | int init_lights(); 106 | 107 | /** Initialise the inputs on the bus (give them addresses) 108 | * 109 | * @returns the number of input devices on the bus 110 | * 111 | */ 112 | int init_inputs(); 113 | 114 | /** Attach a callback when input event is generated 115 | * 116 | * @param status_cb callback to take in the 32 bit event message 117 | */ 118 | void attach(mbed::Callback status_cb); 119 | 120 | /** Detach the callback 121 | */ 122 | void detach(); 123 | 124 | /** Reattach the callback 125 | */ 126 | void reattach(); 127 | 128 | /** Send a standard command on the bus 129 | * 130 | * @param address The address byte for command 131 | * @param opcode The opcode byte 132 | * 133 | */ 134 | void send_command_standard(uint8_t address, uint8_t opcode); 135 | 136 | /** Send a standard command on the bus to input devices 137 | * 138 | * @param address The address byte for command 139 | * @param instance The instance byte for command 140 | * @param opcode The opcode byte 141 | * 142 | */ 143 | void send_command_standard_input(uint8_t address, uint8_t instance, 144 | uint8_t opcode); 145 | 146 | /** Send a special command on the bus 147 | * 148 | * @param address The special command opcode from SpecialCommandOpAddr 149 | * enum 150 | * @param opcode The data for the command 151 | * 152 | */ 153 | void send_command_special(uint8_t address, uint8_t opcode); 154 | 155 | /** Send a special command on the bus to input devices 156 | * 157 | * @param instance The instance byte for command 158 | * @param opcode The opcode byte 159 | * 160 | */ 161 | void send_command_special_input(uint8_t instance, uint8_t opcode); 162 | 163 | /** Send a direct arc power command on the bus 164 | * 165 | * @param address The address byte for command 166 | * @param opcode The opcode byte 167 | * 168 | */ 169 | void send_command_direct(uint8_t address, uint8_t opcode); 170 | 171 | /** Get the address of a group 172 | * 173 | * @param group_number The group number [0-15] 174 | * @returns 175 | * 8 bit address of the group 176 | * 177 | */ 178 | uint8_t get_group_addr(uint8_t group_number); 179 | 180 | /** Add a device to a group 181 | * 182 | * @param addr 8 bit device address 183 | * @param group The group number [0-15] 184 | * @returns 185 | * true if command success 186 | * 187 | */ 188 | bool add_to_group(uint8_t addr, uint8_t group); 189 | 190 | /** Remove a device from a group 191 | * 192 | * @param addr 8 bit device address 193 | * @param group The group number [0-15] 194 | * @returns 195 | * true if command success 196 | * 197 | */ 198 | bool remove_from_group(uint8_t addr, uint8_t group); 199 | 200 | /** Set the light output for a device/group 201 | * 202 | * @param addr 8 bit address (device or group) 203 | * @param level Light output level [0,254] 204 | * NOTE: Refer to section 9.3 of iec62386-102 for dimming curve 205 | * 206 | */ 207 | void set_level(uint8_t addr, uint8_t level); 208 | 209 | /** Turn a device/group off 210 | * 211 | * @param addr 8 bit address (device or group) 212 | * 213 | */ 214 | void turn_off(uint8_t addr); 215 | 216 | /** Turn a device/group on 217 | * 218 | * @param addr 8 bit address (device or group) 219 | * 220 | */ 221 | void turn_on(uint8_t addr); 222 | 223 | /** Get the current light level 224 | * 225 | * @param addr 8 bit address (device or group) 226 | * @returns 227 | * Light level [0, 254] from QUERY ACTUAL LEVEL command 228 | * 229 | */ 230 | uint8_t get_level(uint8_t addr); 231 | 232 | /** Get the current error status 233 | * 234 | * @param addr 8 bit address (device or group) 235 | * @returns 236 | * Error status from QUERY ERROR command 237 | * 238 | */ 239 | uint8_t get_error(uint8_t addr); 240 | 241 | /** Get the fade time and fade rate 242 | * 243 | * @param addr 8 bit address (device or group) 244 | * @returns 245 | * The 8 bit representation of the fade time/fade rate from the QUERY 246 | * FADE TIME/FADE RATE command The answer shall be XXXX YYYYb, where XXXXb 247 | * equals fadeTime and YYYYb equals fadeRate 248 | * 249 | */ 250 | uint8_t get_fade(uint8_t addr); 251 | 252 | /** Get the number of instances on an input device 253 | * 254 | * @param addr 8 bit address of the input device 255 | * @returns 256 | * The number of instances on an input device 0 to 31 257 | * 258 | */ 259 | uint32_t query_instances(uint8_t addr); 260 | 261 | /** Get the color type features 262 | * 263 | * @param addr 8 bit address of the light 264 | * 265 | * @returns 266 | * 8 bit number represnting color type features (page 38 iec62386-209 267 | * bit 0 xy-coordinate capable (0 = No) 268 | * bit 1 Color temperature capable (0 = No) 269 | * bit 2..4 Number of primary colors ([0, 6]) 270 | * bit 5..7 Number RGBWAF channels ([0,6]) 271 | * 272 | */ 273 | uint8_t query_color_type_features(uint8_t addr); 274 | 275 | ColorType get_color_type(uint8_t addr); 276 | 277 | /** Query if the light is capable of color temperature 278 | * 279 | * @param addr 8 bit address of the light 280 | * 281 | * @returns boolean representing support 282 | * 283 | */ 284 | bool query_temperature_capable(uint8_t addr); 285 | 286 | /** Query number of rgbwaf channels 287 | * 288 | * @param addr 8 bit address of the light 289 | * 290 | * @returns integer number of channels 291 | * 292 | */ 293 | uint8_t query_rgbwaf_channels(uint8_t addr); 294 | 295 | /** Set the color 296 | * 297 | * @param addr 8 bit address of the light 298 | * @param r level of red [0,254] 299 | * @param g level of green [0,254] 300 | * @param b level of blue [0,254] 301 | * @param dim level of dim [0,254] 302 | * 303 | */ 304 | void set_color(uint8_t addr, uint8_t r, uint8_t g, uint8_t b, uint8_t dim = 0); 305 | 306 | /** Set the color scene 307 | * 308 | * @param addr 8 bit address of the light 309 | * @param addr 8 bit scene number 310 | * @param r level of red [0,254] 311 | * @param g level of green [0,254] 312 | * @param b level of blue [0,254] 313 | * @param dim level of dim [0,254] 314 | * 315 | */ 316 | void set_color_scene(uint8_t addr, uint8_t scene, uint8_t r, uint8_t g, uint8_t b, uint8_t dim = 0); 317 | 318 | /** Set the color 319 | * 320 | * @param addr 8 bit address of the light 321 | * @param temp light temperature in kelvin [2500,7042] 322 | * 323 | */ 324 | void set_color(uint8_t addr, uint16_t temp); 325 | 326 | /** Set the color scene 327 | * 328 | * @param addr 8 bit address of the light 329 | * @param addr 8 bit scene number 330 | * @param temp light temperature in kelvin [2500,7042] 331 | * 332 | */ 333 | void set_color_scene(uint8_t addr, uint8_t scene, uint16_t temp); 334 | 335 | 336 | /** Set the event scheme -- section 9.6.3 of iec62386-103 337 | * 0 (default) -Instance addressing, using instance type and number. 338 | * 1 - Device addressing, using short address and instance type. 339 | * 2 - Device/instance addressing, using short address and instance number. 340 | * 3 - Device group addressing, using device group and instance type. 341 | * 4 - Instance group addressing, using instance group and type. 342 | * 343 | * @param address The address byte for command 344 | * @param instance The instance byte for command 345 | * @param scheme The scheme [0, 4] 346 | * 347 | */ 348 | void set_event_scheme(uint8_t addr, uint8_t inst, uint8_t scheme); 349 | 350 | /** Set the event scheme -- section 9.6.4 of iec62386-103 351 | * 352 | * @param address The address byte for command 353 | * @param instance The instance byte for command 354 | * @param filter The filter for DTR0 355 | * 356 | */ 357 | void set_event_filter(uint8_t addr, uint8_t inst, uint8_t filter); 358 | 359 | /** Get the instance type -- 9.4.3 of iec62386-103 360 | * 361 | * @param address The address byte for command 362 | * @param instance The instance byte for command 363 | * @returns 364 | * The instance type number [0,31], see InstanceType enum for values 365 | * 366 | */ 367 | uint8_t get_instance_type(uint8_t addr, uint8_t inst); 368 | 369 | /** Get the instance status 370 | * 371 | * @param address The address byte for command 372 | * @param instance The instance byte for command 373 | * @returns 374 | * status -- 255 for enabled, 0 for disabled 375 | * 376 | */ 377 | uint8_t get_instance_status(uint8_t addr, uint8_t inst); 378 | 379 | /** Disable the instance 380 | * 381 | * @param address The address byte for command 382 | * @param instance The instance byte for command 383 | * 384 | */ 385 | void disable_instance(uint8_t addr, uint8_t inst); 386 | 387 | /** Enable the instance 388 | * 389 | * @param address The address byte for command 390 | * @param instance The instance byte for command 391 | * 392 | */ 393 | void enable_instance(uint8_t addr, uint8_t inst); 394 | 395 | /** Get the temperature from a sensor 396 | * 397 | * @param address The address byte for command 398 | * @param instance The instance byte for command 399 | * @returns 400 | * The temperature in celcius 401 | * 402 | */ 403 | float get_temperature(uint8_t addr, uint8_t instance); 404 | 405 | /** Get the humidity from a sensor 406 | * 407 | * @param address The address byte for command 408 | * @param instance The instance byte for command 409 | * @returns 410 | * The humidity percentage 411 | * 412 | */ 413 | float get_humidity(uint8_t addr, uint8_t instance); 414 | 415 | /** Set quiet mode status (event messages on/off 416 | * 417 | * @param on whether quiet mode is on or off 418 | * 419 | */ 420 | void quiet_mode(bool on); 421 | 422 | /** Get the physical minimum 423 | * 424 | * @param addr 8 bit address (device or group) 425 | * @returns 426 | * Physical minimum light output the control gear can operate at [0, 427 | * 254] from QUERY PHYSICAL MINIMUM command 428 | * 429 | */ 430 | uint8_t get_phm(uint8_t addr); 431 | 432 | /** Set the fade rate for a device/group 433 | * 434 | * @param addr 8 bit address (device or group) 435 | * @param rate Fade rate [1, 15] 436 | * NOTE: Refer to section 9.5.3 of iec62386-102 for fade rate calculation 437 | * 438 | */ 439 | void set_fade_rate(uint8_t addr, uint8_t rate); 440 | 441 | /** Set the fade time for a device/group 442 | * 443 | * @param addr 8 bit address (device or group) 444 | * @param rate Fade time [1, 15] 445 | * NOTE: Refer to section 9.5.2 of iec62386-102 for fade time calculation 446 | * 447 | */ 448 | void set_fade_time(uint8_t addr, uint8_t time); 449 | 450 | /** Set the light output for a scene 451 | * 452 | * @param addr 8 bit address (device or group) 453 | * @param scene scene number [0, 15] 454 | * @param level Light output level [0,254] 455 | * 456 | */ 457 | void set_scene(uint8_t addr, uint8_t scene, uint8_t level); 458 | 459 | /** Remove device/group from scene 460 | * 461 | * @param addr 8 bit address (device or group) 462 | * @param scene scene number [0, 15] 463 | * 464 | */ 465 | void remove_from_scene(uint8_t addr, uint8_t scene); 466 | 467 | /** Go to a scene 468 | * 469 | * @param addr 8 bit address (device or group) 470 | * @param scene scene number [0, 15] 471 | * 472 | */ 473 | void go_to_scene(uint8_t addr, uint8_t scene); 474 | 475 | /** Call recv on the bus 476 | * 477 | * @returns the messagein the recv buffer for the bus (encoder class) 478 | * 479 | */ 480 | uint32_t recv(); 481 | 482 | /** Parse the event message 483 | * 484 | * @param msg the 32 bit event message 485 | * @returns the event_msg struct containing addr, instance type, 486 | * and event info 487 | * 488 | */ 489 | event_msg parse_event(uint32_t msg); 490 | 491 | static const uint8_t broadcast_addr = 0xFF; 492 | 493 | // The encoder for the bus signals 494 | ManchesterEncoder encoder; 495 | 496 | int get_num_lights() 497 | { 498 | return num_lights; 499 | } 500 | 501 | int get_num_inputs() 502 | { 503 | return num_inputs; 504 | } 505 | 506 | int get_input_addr_start() 507 | { 508 | return num_lights; 509 | } 510 | 511 | private: 512 | void set_color_temp(uint8_t addr, uint16_t temp); 513 | void set_color_temp(uint8_t addr, uint8_t r, uint8_t g, uint8_t b, uint8_t dim = 0); 514 | 515 | // Some commands must be sent twice, utility function to do that 516 | void send_twice(uint8_t addr, uint8_t opcode); 517 | 518 | /** Assign addresses to the luminaires on the bus 519 | * 520 | * @returns The number of input devices found on bus 521 | * 522 | * NOTE: This process is mostly copied from page 82 of iec62386-102 523 | * The addresses will be in the range [0, number units - 1] 524 | */ 525 | int assign_addresses(bool reset = false); 526 | 527 | /** Assign addresses to the input devices on the bus 528 | * 529 | * @param num_found The number of luminaires already found (so that the 530 | * starting address is last_luminaire + 1) 531 | * @returns The number of unput devices found on bus 532 | * 533 | * NOTE: This process is mostly copied from page 82 of iec62386-102 534 | * The addresses will be in the range [0, number units - 1] 535 | */ 536 | int assign_addresses_input(bool reset = false, int num_found = 0); 537 | 538 | /** Assign addresses to the logical units on the bus 539 | * 540 | * @returns The number of logical units found on bus 541 | * 542 | * NOTE: This process is mostly copied from page 82 of iec62386-102 543 | * The addresses will be in the range [0, number units - 1] 544 | */ 545 | int get_highest_address(); 546 | 547 | /** Set the controller search address for luminaires 548 | * This address will be used in search commands to determine what 549 | * control units have this address or a numerically lower address 550 | * 551 | * @param val Search address valued (only lower 24 bits are used) 552 | * 553 | */ 554 | void set_search_address(uint32_t val); 555 | 556 | /** Set the controller search address for input devices 557 | * This address will be used in search commands to determine what 558 | * control units have this address or a numerically lower address 559 | * 560 | * @param val Search address valued (only lower 24 bits are used) 561 | * 562 | */ 563 | void set_search_address_input(uint32_t val); 564 | 565 | /** Check the response from the bus 566 | * 567 | * @param expected Expected response from the bus 568 | * @returns 569 | * True if response matches expected response 570 | * 571 | */ 572 | bool check_response(uint8_t expected); 573 | 574 | /** Get the index of a control unit 575 | * 576 | * @param addr The address of the device 577 | * @returns The index of the device 578 | */ 579 | int getIndexOfLogicalUnit(uint8_t addr); 580 | 581 | // The number of logical units on the bus 582 | int num_logical_units; 583 | // Number of luminaires on the bus 584 | int num_lights; 585 | // Number of input devices on the bus 586 | int num_inputs; 587 | // Address where input devices start 588 | int inputs_start; 589 | }; 590 | 591 | #endif 592 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DALI-based Driver for Mbed OS 2 | 3 | A driver for DALI lights that adhere to iec62386-102. THe driver also supports DALI sensor instances part of iec62386-103, like presence and motion sensors. 4 | 5 | ## Example Usage - Lights only configuration 6 | 7 | ``` 8 | #include "mbed.h" 9 | #include "DALIDriver.h" 10 | 11 | int main() { 12 | DALIDriver dali(D0, D2); 13 | dali.init(); 14 | int num_devices = dali.get_num_lights(); 15 | printf("Devices on bus: %d\r\n", num_devices); 16 | // Devices will be addressed [0, num_devices-1] 17 | 18 | // Add all devices to group 1 19 | for(int i = 0; i < num_devices; i++) { 20 | dali.add_to_group(i, 1); 21 | } 22 | 23 | // Get the group address of group 1 24 | uint8_t group_addr = dali.get_group_addr(1); 25 | 26 | dali.turn_off(group_addr); // turn entire group off 27 | 28 | dali.set_fade_time(group_addr, 12); // 28 seconds 29 | dali.set_fade_rate(group_addr, 5); // 80 steps/second 30 | 31 | // Set the scene 2 for the group to be at level 254 32 | dali.set_scene(group_addr, 2, 254); 33 | 34 | // Group go to scene 2 35 | dali.go_to_scene(group_addr, 2); 36 | 37 | // wait for 5 seconds 38 | wait(5); 39 | 40 | // Turn off all lights using the broadcast address 41 | dali.turn_off(DALIDriver::broadcast_addr); 42 | } 43 | ``` 44 | 45 | ## Example Usage - Colored lights 46 | 47 | ``` 48 | #include "mbed.h" 49 | #include "DALIDriver.h" 50 | 51 | int main() { 52 | DALIDriver dali(D0, D2); 53 | dali.init(); 54 | int num_devices = dali.get_num_lights(); 55 | printf("Devices on bus: %d\r\n", num_devices); 56 | // Devices will be addressed [0, num_devices-1] 57 | 58 | for(int i = 0; i < num_devices; i++) { 59 | dali.turn_on(i); 60 | uint8_t channels = dali.query_rgbwaf_channels(i); 61 | printf("Channels for device %i: 0x%X\r\n", i, channels); 62 | if (channels == 4) { 63 | printf("RGBD light\r\n"); 64 | // Set red light 65 | dali.set_color(i, 255, 0, 0); 66 | wait(3); 67 | // Set green light 68 | dali.set_color(i, 0, 255, 0); 69 | } 70 | else if (dali.query_temperature_capable(i)) { 71 | printf("Temperature light\r\n"); 72 | // Set very warm color 73 | dali.set_color(i, 2500); 74 | wait(3); 75 | // Set very cold color 76 | dali.set_color(i, 7042); 77 | } 78 | } 79 | ``` 80 | 81 | 82 | ## Example usage - Lights and sensors 83 | 84 | ``` 85 | #include "mbed.h" 86 | #include "DALIDriver.h" 87 | 88 | EventQueue eventQueue; 89 | 90 | void handle_sensor(uint32_t data) 91 | { 92 | printf("Sensor event message: %lu\r\n", data); 93 | event_msg m = controller.parse_event(data); 94 | if (m.inst_type == OCCUPANCY) { 95 | // a 1 in the LSb means motion detected 96 | int motion = (m.info & 1) ? 1 : 0; 97 | printf("Motion captured, occupancy status: %d\r\n", motion); 98 | } 99 | } 100 | 101 | 102 | int main() { 103 | DALIDriver dali(D0, D2); 104 | // Assign addresses to devices on the bus 105 | // Lights will be addressed [0, num_lights-1] 106 | // Instances will be addressed [num_lights, num_lights + num_inputs - 1] 107 | dali.init(); 108 | 109 | // number of lights 110 | int num_lights = dali.get_num_lights(); 111 | // number of input instances 112 | int num_inputs = dali.get_num_inputs(); 113 | 114 | printf("Devices on bus: %d\r\n", num_lights + num_inputs); 115 | 116 | // Handle motion events 117 | dali.attach(eventQueue.event(handle_sensor)); 118 | 119 | // Add all devices to group 1 120 | for(int i = 0; i < num_lights; i++) { 121 | dali.add_to_group(i, 1); 122 | } 123 | 124 | // Get the group address of group 1 125 | uint8_t group_addr = dali.get_group_addr(1); 126 | 127 | dali.turn_off(group_addr); // turn entire group off 128 | 129 | dali.set_fade_time(group_addr, 12); // 28 seconds 130 | dali.set_fade_rate(group_addr, 5); // 80 steps/second 131 | 132 | // Set the scene 2 for the group to be at level 254 133 | dali.set_scene(group_addr, 2, 254); 134 | 135 | // Group go to scene 2 136 | dali.go_to_scene(group_addr, 2); 137 | } 138 | ``` 139 | 140 | -------------------------------------------------------------------------------- /manchester/encoder.cpp: -------------------------------------------------------------------------------- 1 | /* Manchester Encoder Library 2 | * Copyright (c) 2018 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "encoder.h" 18 | 19 | ManchesterEncoder::ManchesterEncoder(PinName out_pin, PinName in_pin, int baud, 20 | bool idle_state) 21 | : _output_pin(out_pin), _input_pin(in_pin, PullUp) 22 | { 23 | _idle_state = idle_state; 24 | _output_pin = idle_state; 25 | // Half bit time in seconds 26 | float time_s = 1.0 / (2.0 * (float)baud); 27 | // Half bit time in microseconds 28 | _half_bit_time = (int)(time_s * 1000000.0); 29 | data_ready = false; 30 | recv_data = 0; 31 | bit_recv_total = 8; 32 | } 33 | 34 | // Blocking receive call 35 | int ManchesterEncoder::recv() 36 | { 37 | // -1 means no data ready in timeout period 38 | int ret = -1; 39 | // Wait for timeout between response 40 | wait_us(2400); 41 | // Calculate timer stop time, 9 recv bits, stop condition, half bit extra 42 | int stop_time = (_half_bit_time * 2 * 9) + 2400 + _half_bit_time; 43 | // Start a timer 44 | Timer t; 45 | t.start(); 46 | // While reading data 47 | while (rx_in_progress && t.read_us() < stop_time) { 48 | }; 49 | t.stop(); 50 | if (data_ready) { 51 | // If there is data, clear our buffer 52 | ret = recv_data; 53 | recv_data = 0; 54 | } 55 | return ret; 56 | } 57 | 58 | void ManchesterEncoder::send_24(uint32_t data_out) 59 | { 60 | // We don't want to be preempted because this is time sensitive 61 | core_util_critical_section_enter(); 62 | clear_interrupts(); 63 | // Send start condition 64 | _output_pin = !_idle_state; 65 | wait_us(_half_bit_time); 66 | _output_pin = _idle_state; 67 | wait_us(_half_bit_time); 68 | // Send the data 69 | for (int i = 0; i < 24; i++) { 70 | // Send the actual MSb 71 | _output_pin = (bool)(data_out & 0x800000); 72 | wait_us(_half_bit_time); 73 | // Send the inverted MSb 74 | _output_pin = !((bool)(data_out & 0x800000)); 75 | wait_us(_half_bit_time); 76 | // Shift to next bit 77 | data_out = data_out << 1; 78 | } 79 | // Send the stop condition 80 | _output_pin = _idle_state; 81 | bit_recv_total = 8; 82 | core_util_critical_section_exit(); 83 | _input_pin.rise(callback(this, &ManchesterEncoder::rise_handler)); 84 | wait_us(13500); 85 | } 86 | 87 | void ManchesterEncoder::set_recv_frame_length(int num) 88 | { 89 | bit_recv_total = num; 90 | } 91 | 92 | void ManchesterEncoder::send(uint16_t data_out) 93 | { 94 | // We don't want to be preempted because this is time sensitive 95 | core_util_critical_section_enter(); 96 | clear_interrupts(); 97 | // Send start condition 98 | _output_pin = !_idle_state; 99 | wait_us(_half_bit_time); 100 | _output_pin = _idle_state; 101 | wait_us(_half_bit_time); 102 | // Send the data 103 | for (int i = 0; i < 16; i++) { 104 | // Send the actual MSb 105 | _output_pin = (bool)(data_out & 0x8000); 106 | wait_us(_half_bit_time); 107 | // Send the inverted MSb 108 | _output_pin = !((bool)(data_out & 0x8000)); 109 | wait_us(_half_bit_time); 110 | // Shift to next bit 111 | data_out = data_out << 1; 112 | } 113 | // Send the stop condition 114 | _output_pin = _idle_state; 115 | bit_recv_total = 8; 116 | core_util_critical_section_exit(); 117 | _input_pin.rise(callback(this, &ManchesterEncoder::rise_handler)); 118 | wait_us(13500); 119 | } 120 | 121 | void ManchesterEncoder::attach(mbed::Callback status_cb) 122 | { 123 | bit_recv_total = 24; 124 | _sensor_event_cb = status_cb; 125 | _input_pin.rise(callback(this, &ManchesterEncoder::rise_handler)); 126 | } 127 | 128 | void ManchesterEncoder::detach() 129 | { 130 | // Wait for the done flag or 100 ms max 131 | event_flags.wait_all(DONE_FLAG, 100); 132 | bit_recv_total = 8; 133 | if (_sensor_event_cb) { 134 | _sensor_event_cb_save = _sensor_event_cb; 135 | _sensor_event_cb = NULL; 136 | } 137 | clear_interrupts(); 138 | } 139 | 140 | void ManchesterEncoder::reattach() 141 | { 142 | attach(_sensor_event_cb_save); 143 | } 144 | 145 | void ManchesterEncoder::clear_interrupts() 146 | { 147 | _input_pin.rise(0); 148 | _input_pin.fall(0); 149 | } 150 | 151 | void ManchesterEncoder::stop() 152 | { 153 | clear_interrupts(); 154 | if (rx_in_progress) { 155 | data_ready = true; 156 | } 157 | rx_in_progress = false; 158 | // Call sensor event handler 159 | if (_sensor_event_cb) 160 | _sensor_event_cb(recv_data); 161 | event_flags.set(DONE_FLAG); 162 | _input_pin.rise(callback(this, &ManchesterEncoder::rise_handler)); 163 | } 164 | 165 | void ManchesterEncoder::irq_handler() 166 | { 167 | clear_interrupts(); 168 | rx_in_progress = true; 169 | data_ready = false; 170 | // Clear any stop timers 171 | t2.detach(); 172 | t1.attach_us(callback(this, &ManchesterEncoder::read_state), 173 | 1.5 * (float)_half_bit_time); 174 | t2.attach_us(callback(this, &ManchesterEncoder::stop), 2450); 175 | } 176 | 177 | void ManchesterEncoder::read_state() 178 | { 179 | int state = _input_pin.read(); 180 | if (bit_count < bit_recv_total) { 181 | uint32_t mask = ((bool)state) << ((bit_recv_total - 1) - bit_count++); 182 | recv_data |= mask; 183 | } 184 | if (state == 0) { 185 | _input_pin.rise(callback(this, &ManchesterEncoder::irq_handler)); 186 | } else { 187 | _input_pin.fall(callback(this, &ManchesterEncoder::irq_handler)); 188 | } 189 | } 190 | 191 | void ManchesterEncoder::rise_handler() 192 | { 193 | bit_count = 0; 194 | recv_data = 0; 195 | clear_interrupts(); 196 | // fall handler called in less than 1.5*_half_bit_time means start condition 197 | _input_pin.fall(callback(this, &ManchesterEncoder::irq_handler)); 198 | // Stop condition if rise handler is not called in time 199 | t2.attach_us(callback(this, &ManchesterEncoder::stop), 200 | 1.5 * (float)_half_bit_time); 201 | } 202 | -------------------------------------------------------------------------------- /manchester/encoder.h: -------------------------------------------------------------------------------- 1 | /* Manchester Encoder Library 2 | * Copyright (c) 2018 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #ifndef MAN_ENCODING_H 17 | #define MAN_ENCODING_H 18 | 19 | #include "mbed.h" 20 | 21 | #define DONE_FLAG (1UL << 0) 22 | 23 | struct event_msg { 24 | uint8_t addr; 25 | uint8_t inst_type; 26 | uint16_t info; 27 | }; 28 | 29 | class ManchesterEncoder { 30 | public: 31 | // Flag data ready 32 | volatile bool data_ready; 33 | 34 | ManchesterEncoder(PinName out_pin, PinName in_pin, int baud, 35 | bool idle_state = 0); 36 | 37 | // Blocking receive call 38 | int recv(); 39 | 40 | void send_24(uint32_t data_out); 41 | 42 | void set_recv_frame_length(int num); 43 | 44 | void send(uint16_t data_out); 45 | 46 | void attach(mbed::Callback status_cb); 47 | 48 | void detach(); 49 | 50 | void reattach(); 51 | 52 | private: 53 | void clear_interrupts(); 54 | 55 | void stop(); 56 | 57 | void irq_handler(); 58 | 59 | void read_state(); 60 | 61 | void rise_handler(); 62 | 63 | // Pin to output encoded data 64 | DigitalOut _output_pin; 65 | // Pin to read encoded data 66 | InterruptIn _input_pin; 67 | // Half the time for each bit (1/(2*baud)) 68 | int _half_bit_time; 69 | volatile uint32_t recv_data; 70 | volatile uint8_t bit_count; 71 | volatile bool rx_in_progress; 72 | // Total amount of bits expected 73 | volatile uint8_t bit_recv_total; 74 | bool _idle_state; 75 | Timeout t1; 76 | Timeout t2; 77 | EventFlags event_flags; 78 | 79 | Callback _sensor_event_cb; 80 | Callback _sensor_event_cb_save; 81 | }; 82 | 83 | #endif 84 | --------------------------------------------------------------------------------